[Tizen] Add model-loader to abstract gltf2-loader and dli-loader 24/289424/1
authorseungho baek <sbsh.baek@samsung.com>
Mon, 20 Feb 2023 08:19:39 +0000 (17:19 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Tue, 7 Mar 2023 11:42:42 +0000 (20:42 +0900)
 - Like glb, we need to add some new type of model.
 - This abstraction can make easy to add new model file format.

Change-Id: Ib84dd28c082903295ff1295a5c2d4da9a74ef8ca

30 files changed:
automated-tests/src/dali-scene3d-internal/CMakeLists.txt
automated-tests/src/dali-scene3d-internal/utc-Dali-DliLoaderImpl.cpp [new file with mode: 0644]
automated-tests/src/dali-scene3d-internal/utc-Dali-Gltf2LoaderImpl.cpp [new file with mode: 0644]
automated-tests/src/dali-scene3d/CMakeLists.txt
automated-tests/src/dali-scene3d/utc-Dali-DliLoader.cpp [deleted file]
automated-tests/src/dali-scene3d/utc-Dali-Gltf2Loader.cpp [deleted file]
automated-tests/src/dali-scene3d/utc-Dali-ResourceBundle.cpp
automated-tests/src/dali-scene3d/utc-Dali-ShaderDefinitionFactory.cpp
dali-scene3d/internal/common/model-cache-manager.cpp
dali-scene3d/internal/common/model-cache-manager.h
dali-scene3d/internal/common/model-load-task.cpp
dali-scene3d/internal/common/model-load-task.h
dali-scene3d/internal/controls/model/model-impl.cpp
dali-scene3d/internal/controls/model/model-impl.h
dali-scene3d/internal/file.list
dali-scene3d/internal/loader/dli-loader-impl.cpp [new file with mode: 0644]
dali-scene3d/internal/loader/dli-loader-impl.h [new file with mode: 0644]
dali-scene3d/internal/loader/gltf2-loader-impl.cpp [new file with mode: 0644]
dali-scene3d/internal/loader/gltf2-loader-impl.h [new file with mode: 0644]
dali-scene3d/internal/loader/model-loader-impl.h [new file with mode: 0644]
dali-scene3d/public-api/file.list
dali-scene3d/public-api/loader/dli-input-parameter.h [new file with mode: 0644]
dali-scene3d/public-api/loader/dli-loader.cpp [deleted file]
dali-scene3d/public-api/loader/dli-loader.h [deleted file]
dali-scene3d/public-api/loader/gltf2-loader.cpp [deleted file]
dali-scene3d/public-api/loader/gltf2-loader.h [deleted file]
dali-scene3d/public-api/loader/model-loader.cpp [new file with mode: 0644]
dali-scene3d/public-api/loader/model-loader.h [new file with mode: 0644]
dali-scene3d/public-api/loader/resource-bundle.cpp
dali-scene3d/public-api/loader/resource-bundle.h

index 5af964f16175db1f6f73f92bc26a2ad0a13203d6..0109332e888d894b7d84164f00a1f8a7a3a71da3 100755 (executable)
@@ -7,7 +7,9 @@ SET(CAPI_LIB "dali-scene3d")
 
 # List of test case sources (Only these get parsed for test cases)
 SET(TC_SOURCES
+  utc-Dali-DliLoaderImpl.cpp
   utc-Dali-Gltf2Asset.cpp
+  utc-Dali-Gltf2LoaderImpl.cpp
   utc-Dali-Hash.cpp
   utc-Dali-JsonReader.cpp
   utc-Dali-JsonUtil.cpp
diff --git a/automated-tests/src/dali-scene3d-internal/utc-Dali-DliLoaderImpl.cpp b/automated-tests/src/dali-scene3d-internal/utc-Dali-DliLoaderImpl.cpp
new file mode 100644 (file)
index 0000000..0610deb
--- /dev/null
@@ -0,0 +1,712 @@
+/*
+ * 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.
+ *
+ */
+
+// Enable debug log for test coverage
+#define DEBUG_ENABLED 1
+
+#include <dali-scene3d/internal/loader/dli-loader-impl.h>
+#include <dali-scene3d/internal/loader/json-util.h>
+#include <dali-scene3d/public-api/loader/load-result.h>
+#include <dali-scene3d/public-api/loader/resource-bundle.h>
+#include <dali-scene3d/public-api/loader/scene-definition.h>
+#include <dali-test-suite-utils.h>
+#include <string_view>
+
+using namespace Dali;
+using namespace Dali::Scene3D::Loader;
+
+namespace
+{
+void ConfigureBlendShapeShaders(ResourceBundle& resources, const SceneDefinition& scene, Actor root, std::vector<BlendshapeShaderConfigurationRequest>&& requests)
+{
+  std::vector<std::string> errors;
+  auto                     onError = [&errors](const std::string& msg)
+  {
+    errors.push_back(msg);
+  };
+
+  if(!scene.ConfigureBlendshapeShaders(resources, root, std::move(requests), onError))
+  {
+    ExceptionFlinger flinger(ASSERT_LOCATION);
+    for(auto& msg : errors)
+    {
+      flinger << msg << '\n';
+    }
+  }
+}
+
+struct Context
+{
+  ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type)
+  {
+    return TEST_RESOURCE_DIR "/";
+  };
+
+  ResourceBundle                        resources;
+  SceneDefinition                       scene;
+  std::vector<CameraParameters>         cameraParameters;
+  std::vector<LightParameters>          lights;
+  std::vector<AnimationDefinition>      animations;
+  std::vector<AnimationGroupDefinition> animGroups;
+
+  SceneMetadata metaData;
+
+  LoadResult output{
+    resources,
+    scene,
+    metaData,
+    animations,
+    animGroups,
+    cameraParameters,
+    lights};
+
+  Dali::Scene3D::Loader::DliInputParameter       input;
+  std::vector<std::string>                       errors;
+  Dali::Scene3D::Loader::Internal::DliLoaderImpl loader;
+
+  StringCallback onError = [this](const std::string& error)
+  {
+    errors.push_back(error);
+    printf("%s\n", error.c_str());
+  };
+
+  Context()
+  {
+    input.mAnimationsPath = pathProvider(ResourceType::Mesh);
+    loader.SetErrorCallback(onError);
+    loader.SetInputParameter(input);
+  }
+};
+
+bool StringHasTokens(const char* string, const std::vector<const char*>& tokens)
+{
+  for(auto& token : tokens)
+  {
+    auto result = strstr(string, token);
+    if(nullptr == result)
+    {
+      return false;
+    }
+    string = result + strlen(token);
+  }
+  return true;
+}
+
+} // namespace
+
+int UtcDaliDliLoaderLoadSceneNotFound(void)
+{
+  Context ctx;
+
+  DALI_TEST_EQUAL(ctx.loader.LoadModel("does_not_exist.dli", ctx.output), false);
+
+  auto error = ctx.loader.GetParseError();
+  DALI_TEST_CHECK(StringHasTokens(error.c_str(), {"Empty source buffer to parse."}));
+
+  END_TEST;
+}
+
+int UtcDaliDliLoaderLoadSceneFailParse(void)
+{
+  Context ctx;
+
+  auto path = ctx.pathProvider(ResourceType::Mesh) + "invalid.gltf";
+  DALI_TEST_EQUAL(ctx.loader.LoadModel(path, ctx.output), false);
+
+  auto error = ctx.loader.GetParseError();
+  DALI_TEST_CHECK(StringHasTokens(error.c_str(), {"Unexpected character."}));
+
+  END_TEST;
+}
+
+int UtcDaliDliLoaderLoadSceneAssertions(void)
+{
+  const std::pair<std::string, std::string> pathExceptionPairs[]{
+    // from RequireChild()
+    {"scenes-nodes-missing", "Failed to find child node"},
+    {"scenes-missing", "Failed to find child node"},
+    {"nodes-missing", "Failed to find child node"},
+    // from ParseSceneInternal()
+    {"scene-out-of-bounds", "out of bounds"},
+    {"nodes-invalid-type", "invalid type; array required"},
+    {"nodes-array-empty", "must define a node id"},
+    {"root-id-invalid", "invalid value for root node index"},
+    {"root-id-out-of-bounds", "out of bounds"},
+    {"root-node-invalid-type", "invalid JSON type; object required"},
+    // from ParseSkeletons()
+    {"skeleton-node-missing", "Missing required attribute"},
+    {"skeleton-root-not-found", "not defined"},
+    // from ParseShaders()
+    {"shader-vertex-missing", "Missing vertex / fragment shader"},
+    {"shader-fragment-missing", "Missing vertex / fragment shader"},
+    // from ParseMeshes()
+    {"mesh-uri-missing", "Missing required attribute"},
+    {"mesh-indices-read-fail", "Failed to read indices"},
+    {"mesh-positions-read-fail", "Failed to read positions"},
+    // from ParseMaterials()
+    {"material-environment-out-of-bounds", "out of bounds"},
+    // from ParseNodes()
+    {"node-model-mesh-missing", "Missing mesh"},
+    {"node-arc-mesh-missing", "Missing mesh"},
+    {"node-animated-image-mesh-missing", "Missing mesh"},
+    {"node-renderable-mesh-invalid-type", "Invalid Mesh index type"},
+    {"node-renderable-mesh-out-of-bounds", "out of bounds"},
+    {"node-child-invalid-type", "invalid index type"},
+    // from ParseAnimations()
+    {"animation-failed-to-open", "Failed to open animation data"}};
+  for(auto& i : pathExceptionPairs)
+  {
+    Context ctx;
+
+    auto path = ctx.pathProvider(ResourceType::Mesh) + "dli/" + i.first + ".dli";
+    printf("\n\n%s: %s\n", path.c_str(), i.second.c_str());
+    DALI_TEST_ASSERTION(ctx.loader.LoadModel(path, ctx.output), i.second.c_str());
+  }
+
+  END_TEST;
+}
+
+int UtcDaliDliLoaderLoadSceneExercise(void)
+{
+  Context ctx;
+
+  auto path = ctx.pathProvider(ResourceType::Mesh) + "exercise.dli";
+  DALI_TEST_CHECK(ctx.loader.LoadModel(path, ctx.output));
+  DALI_TEST_CHECK(ctx.errors.empty());
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 2u);
+  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "Backdrop"); // default scene is scene 1 - this one.
+  DALI_TEST_EQUAL(scene.GetNode(roots[1])->mName, "ExerciseDemo");
+
+  DALI_TEST_EQUAL(scene.GetNodeCount(), 96u);
+
+  auto& resources = ctx.resources;
+  DALI_TEST_EQUAL(resources.mMeshes.size(), 11u);
+  DALI_TEST_EQUAL(resources.mMaterials.size(), 13u);
+  DALI_TEST_EQUAL(resources.mShaders.size(), 5u);
+  DALI_TEST_EQUAL(resources.mEnvironmentMaps.size(), 2u);
+  DALI_TEST_EQUAL(resources.mSkeletons.size(), 1u);
+
+  DALI_TEST_EQUAL(ctx.cameraParameters.size(), 1u);
+  DALI_TEST_EQUAL(ctx.lights.size(), 1u);
+  DALI_TEST_EQUAL(ctx.animations.size(), 18u);
+  DALI_TEST_EQUAL(ctx.animGroups.size(), 16u);
+
+  ViewProjection viewProjection;
+  Transforms     xforms{
+    MatrixStack{},
+    viewProjection};
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+  };
+
+  Customization::Choices choices;
+
+  TestApplication app;
+
+  Actor root = Actor::New();
+  SetActorCentered(root);
+  for(auto iRoot : scene.GetRoots())
+  {
+    auto resourceRefs = resources.CreateRefCounter();
+    scene.CountResourceRefs(iRoot, choices, resourceRefs);
+    resources.mReferenceCounts = std::move(resourceRefs);
+    resources.CountEnvironmentReferences();
+    resources.LoadResources(ctx.pathProvider);
+    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
+    {
+      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
+      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
+      ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
+      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      root.Add(actor);
+    }
+  }
+
+  DALI_TEST_EQUAL(root.GetChildCount(), 2u);
+  DALI_TEST_EQUAL(root.GetChildAt(0).GetProperty(Actor::Property::NAME).Get<std::string>(), "Backdrop");
+  DALI_TEST_EQUAL(root.GetChildAt(1).GetProperty(Actor::Property::NAME).Get<std::string>(), "ExerciseDemo");
+
+  END_TEST;
+}
+
+int UtcDaliDliLoaderLoadSceneMorph(void)
+{
+  Context ctx;
+
+  std::vector<std::string> metadata;
+  uint32_t                 metadataCount = 0;
+  ctx.input.mPreNodeCategoryProcessors.push_back({"metadata",
+                                                  [&](const Property::Array& array, StringCallback)
+                                                  {
+                                                    std::string key, value;
+                                                    for(uint32_t i0 = 0, i1 = array.Count(); i0 < i1; ++i0)
+                                                    {
+                                                      auto& data = array.GetElementAt(i0);
+                                                      DALI_TEST_EQUAL(data.GetType(), Property::MAP);
+
+                                                      auto map   = data.GetMap();
+                                                      auto key   = map->Find("key");
+                                                      auto value = map->Find("value");
+                                                      DALI_TEST_EQUAL(key->GetType(), Property::STRING);
+                                                      DALI_TEST_EQUAL(value->GetType(), Property::STRING);
+                                                      metadata.push_back(key->Get<std::string>() + ":" + value->Get<std::string>());
+
+                                                      ++metadataCount;
+                                                    }
+                                                  }});
+
+  std::vector<std::string> behaviors;
+  uint32_t                 behaviorCount = 0;
+  ctx.input.mPostNodeCategoryProcessors.push_back({"behaviors",
+                                                   [&](const Property::Array& array, StringCallback)
+                                                   {
+                                                     for(uint32_t i0 = 0, i1 = array.Count(); i0 < i1; ++i0)
+                                                     {
+                                                       auto& data = array.GetElementAt(i0);
+                                                       DALI_TEST_EQUAL(data.GetType(), Property::MAP);
+
+                                                       auto map   = data.GetMap();
+                                                       auto event = map->Find("event");
+                                                       auto url   = map->Find("url");
+                                                       DALI_TEST_EQUAL(event->GetType(), Property::STRING);
+                                                       DALI_TEST_EQUAL(url->GetType(), Property::STRING);
+                                                       behaviors.push_back(event->Get<std::string>() + ":" + url->Get<std::string>());
+
+                                                       ++behaviorCount;
+                                                     }
+                                                   }});
+
+  size_t numNodes                  = 0;
+  ctx.input.mNodePropertyProcessor = [&](const NodeDefinition&, const Property::Map&, StringCallback)
+  {
+    ++numNodes;
+  };
+
+  auto path = ctx.pathProvider(ResourceType::Mesh) + "morph.dli";
+  DALI_TEST_CHECK(ctx.loader.LoadModel(path, ctx.output));
+  DALI_TEST_CHECK(ctx.errors.empty());
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 1u);
+  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "HeadTest_002");
+
+  DALI_TEST_EQUAL(numNodes, 3u);
+  DALI_TEST_EQUAL(scene.GetNodeCount(), numNodes);
+
+  auto& resources = ctx.resources;
+  DALI_TEST_EQUAL(resources.mMeshes.size(), 2u);
+  DALI_TEST_EQUAL(resources.mMaterials.size(), 1u);
+  DALI_TEST_EQUAL(resources.mShaders.size(), 5u);
+  DALI_TEST_EQUAL(resources.mEnvironmentMaps.size(), 2u);
+  DALI_TEST_EQUAL(resources.mSkeletons.size(), 0u);
+
+  DALI_TEST_EQUAL(ctx.cameraParameters.size(), 1u);
+  DALI_TEST_EQUAL(ctx.lights.size(), 1u);
+  DALI_TEST_EQUAL(ctx.animations.size(), 1u);
+  DALI_TEST_EQUAL(ctx.animGroups.size(), 0u);
+
+  DALI_TEST_EQUAL(metadata.size(), 4u);
+  DALI_TEST_EQUAL(behaviors.size(), 1u);
+
+  ViewProjection viewProjection;
+  Transforms     xforms{
+    MatrixStack{},
+    viewProjection};
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+  };
+
+  Customization::Choices choices;
+
+  TestApplication app;
+
+  Actor root = Actor::New();
+  SetActorCentered(root);
+  for(auto iRoot : scene.GetRoots())
+  {
+    auto resourceRefs = resources.CreateRefCounter();
+    scene.CountResourceRefs(iRoot, choices, resourceRefs);
+    resources.mReferenceCounts = std::move(resourceRefs);
+    resources.CountEnvironmentReferences();
+    resources.LoadResources(ctx.pathProvider);
+    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
+    {
+      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
+      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
+      ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
+      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      root.Add(actor);
+    }
+  }
+
+  DALI_TEST_EQUAL(root.GetChildCount(), 1u);
+  DALI_TEST_EQUAL(root.GetChildAt(0).GetProperty(Actor::Property::NAME).Get<std::string>(), "HeadTest_002");
+
+  END_TEST;
+}
+
+int UtcDaliDliLoaderLoadSceneArc(void)
+{
+  Context ctx;
+
+  auto path = ctx.pathProvider(ResourceType::Mesh) + "arc.dli";
+  DALI_TEST_CHECK(ctx.loader.LoadModel(path, ctx.output));
+  DALI_TEST_CHECK(ctx.errors.empty());
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 1u);
+  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "root");
+
+  DALI_TEST_EQUAL(scene.GetNodeCount(), 2u);
+
+  auto& resources = ctx.resources;
+  DALI_TEST_EQUAL(resources.mMeshes.size(), 1u);
+  DALI_TEST_EQUAL(resources.mMaterials.size(), 1u);
+  DALI_TEST_EQUAL(resources.mShaders.size(), 1u);
+  DALI_TEST_EQUAL(resources.mEnvironmentMaps.size(), 1u);
+  DALI_TEST_EQUAL(resources.mSkeletons.size(), 0u);
+
+  DALI_TEST_EQUAL(ctx.cameraParameters.size(), 0u);
+  DALI_TEST_EQUAL(ctx.lights.size(), 0u);
+  DALI_TEST_EQUAL(ctx.animations.size(), 0u);
+  DALI_TEST_EQUAL(ctx.animGroups.size(), 0u);
+
+  ViewProjection viewProjection;
+  Transforms     xforms{
+    MatrixStack{},
+    viewProjection};
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+  };
+
+  Customization::Choices choices;
+
+  TestApplication app;
+
+  Actor root = Actor::New();
+  SetActorCentered(root);
+  for(auto iRoot : scene.GetRoots())
+  {
+    auto resourceRefs = resources.CreateRefCounter();
+    scene.CountResourceRefs(iRoot, choices, resourceRefs);
+    resources.mReferenceCounts = std::move(resourceRefs);
+    resources.CountEnvironmentReferences();
+    resources.LoadResources(ctx.pathProvider);
+    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
+    {
+      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
+      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
+      ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
+      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      root.Add(actor);
+    }
+  }
+
+  DALI_TEST_EQUAL(root.GetChildCount(), 1u);
+  DALI_TEST_EQUAL(root.GetChildAt(0).GetProperty(Actor::Property::NAME).Get<std::string>(), "root");
+
+  END_TEST;
+}
+
+int UtcDaliDliLoaderLoadSceneShaderUniforms(void)
+{
+  Context ctx;
+
+  auto path = ctx.pathProvider(ResourceType::Mesh) + "dli/shader-uniforms.dli";
+  DALI_TEST_CHECK(ctx.loader.LoadModel(path, ctx.output));
+  DALI_TEST_EQUAL(ctx.errors.size(), 1u);
+  DALI_TEST_CHECK(ctx.errors[0].find("failed to infer type") != std::string::npos);
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 1u);
+  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "root");
+
+  DALI_TEST_EQUAL(scene.GetNodeCount(), 1u);
+
+  auto& resources = ctx.resources;
+  DALI_TEST_EQUAL(resources.mMeshes.size(), 0u);
+  DALI_TEST_EQUAL(resources.mMaterials.size(), 0u);
+  DALI_TEST_EQUAL(resources.mShaders.size(), 1u);
+  DALI_TEST_EQUAL(resources.mEnvironmentMaps.size(), 0u);
+  DALI_TEST_EQUAL(resources.mSkeletons.size(), 0u);
+
+  auto raw = resources.mShaders[0].first.LoadRaw(ctx.pathProvider(ResourceType::Shader));
+
+  TestApplication app;
+
+  auto shader = resources.mShaders[0].first.Load(std::move(raw));
+  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uBool")).Get<float>(), 1.0f);
+  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uInt")).Get<float>(), 255.0f);
+  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uFloat")).Get<float>(), -0.5f);
+  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uVec2")).Get<Vector2>(), Vector2(100.0f, -100.0f));
+  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uVec3")).Get<Vector3>(), Vector3(50.0f, 0.f, -200.0f));
+  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uVec4")).Get<Vector4>(), Vector4(0.1774f, 1.0f, 0.5333f, 0.7997f));
+  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uMat3")).Get<Matrix3>(), Matrix3(9.0f, 8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f));
+
+  Matrix expectedMatrix;
+  expectedMatrix.SetTransformComponents(Vector3::ONE * 8.0, Quaternion::IDENTITY, Vector3::ZERO);
+  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uMat4")).Get<Matrix>(), expectedMatrix);
+
+  END_TEST;
+}
+
+int UtcDaliDliLoaderLoadSceneExtras(void)
+{
+  Context ctx;
+
+  auto path = ctx.pathProvider(ResourceType::Mesh) + "dli/extras.dli";
+  DALI_TEST_CHECK(ctx.loader.LoadModel(path, ctx.output));
+  DALI_TEST_EQUAL(ctx.errors.size(), 3u);
+  DALI_TEST_CHECK(ctx.errors[0].find("already defined; overriding") != std::string::npos);
+  DALI_TEST_CHECK(ctx.errors[1].find("empty string is invalid for name") != std::string::npos);
+  DALI_TEST_CHECK(ctx.errors[2].find("failed to interpret value") != std::string::npos);
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 1u);
+  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "root");
+
+  DALI_TEST_EQUAL(scene.GetNodeCount(), 1u);
+
+  ViewProjection viewProjection;
+  Transforms     xforms{
+    MatrixStack{},
+    viewProjection};
+  auto&                        resources = ctx.resources;
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+  };
+
+  Customization::Choices choices;
+
+  TestApplication app;
+  Actor           actor = scene.CreateNodes(0, choices, nodeParams);
+
+  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("fudgeFactor")).Get<float>(), 9000.1f);
+  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("fudgeVector")).Get<Vector2>(), Vector2(-.25f, 17.f));
+  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("isThisTheRealLife")).Get<bool>(), true);
+  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("isThisJustFantasy")).Get<bool>(), false);
+  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("velocity")).Get<Vector3>(), Vector3(.1f, 58.f, -.2f));
+  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("frameOfReference")).Get<Matrix>(), Matrix::IDENTITY);
+
+  END_TEST;
+}
+
+int UtcDaliDliLoaderLoadSceneConstraints(void)
+{
+  Context ctx;
+
+  auto path = ctx.pathProvider(ResourceType::Mesh) + "dli/constraints.dli";
+  DALI_TEST_CHECK(ctx.loader.LoadModel(path, ctx.output));
+  DALI_TEST_EQUAL(ctx.errors.size(), 1u);
+  DALI_TEST_CHECK(ctx.errors[0].find("invalid", ctx.errors[0].find("node ID")) != std::string::npos);
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 1u);
+  DALI_TEST_EQUAL(scene.GetNode(0)->mName, "root");
+  DALI_TEST_EQUAL(scene.GetNode(1)->mName, "Alice");
+  DALI_TEST_EQUAL(scene.GetNode(2)->mName, "Bob");
+  DALI_TEST_EQUAL(scene.GetNode(3)->mName, "Charlie");
+
+  DALI_TEST_EQUAL(scene.GetNodeCount(), 4u);
+
+  ViewProjection viewProjection;
+  Transforms     xforms{
+    MatrixStack{},
+    viewProjection};
+  auto&                        resources = ctx.resources;
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+  };
+
+  Customization::Choices choices;
+
+  TestApplication app;
+
+  Actor root    = scene.CreateNodes(0, choices, nodeParams);
+  Actor alice   = root.FindChildByName("Alice");
+  Actor bob     = root.FindChildByName("Bob");
+  Actor charlie = root.FindChildByName("Charlie");
+
+  DALI_TEST_EQUAL(nodeParams.mConstrainables.size(), 3u);
+  DALI_TEST_EQUAL(bob.GetProperty(bob.GetPropertyIndex("angularVelocity")).Get<Vector2>(), Vector2(-0.5, 0.0004));
+
+  ctx.errors.clear();
+  scene.ApplyConstraints(root, std::move(nodeParams.mConstrainables), ctx.onError);
+  DALI_TEST_CHECK(ctx.errors.empty());
+
+  app.GetScene().Add(root);
+  app.SendNotification();
+  app.Render();
+  app.SendNotification();
+  app.Render();
+
+  DALI_TEST_EQUAL(charlie.GetCurrentProperty(Actor::Property::ORIENTATION), alice.GetProperty(Actor::Property::ORIENTATION));
+  DALI_TEST_EQUAL(charlie.GetCurrentProperty(Actor::Property::POSITION), bob.GetProperty(Actor::Property::POSITION));
+  DALI_TEST_EQUAL(charlie.GetCurrentProperty(charlie.GetPropertyIndex("angularVelocity")), bob.GetProperty(bob.GetPropertyIndex("angularVelocity")));
+
+  END_TEST;
+}
+
+int UtcDaliDliLoaderNodeProcessor(void)
+{
+  Context ctx;
+
+  std::vector<Property::Map> nodeMaps;
+  ctx.input.mNodePropertyProcessor = [&](const NodeDefinition&, Property::Map&& map, StringCallback)
+  {
+    nodeMaps.push_back(map);
+  };
+
+  auto path = ctx.pathProvider(ResourceType::Mesh) + "dli/node-processor.dli";
+  DALI_TEST_CHECK(ctx.loader.LoadModel(path, ctx.output));
+
+  DALI_TEST_EQUAL(nodeMaps.size(), 2u);
+  DALI_TEST_EQUAL(nodeMaps[0].Count(), 5u);
+  DALI_TEST_EQUAL(nodeMaps[0].Find("name")->Get<std::string>(), "rootA");
+  DALI_TEST_EQUAL(nodeMaps[0].Find("nickname")->Get<std::string>(), "same as name");
+  DALI_TEST_EQUAL(nodeMaps[0].Find("favourite number")->Get<int32_t>(), 63478);
+
+  auto propArray = nodeMaps[0].Find("array");
+  DALI_TEST_EQUAL(propArray->GetType(), Property::ARRAY);
+
+  auto array = propArray->GetArray();
+  DALI_TEST_EQUAL(array->Count(), 5);
+  DALI_TEST_EQUAL(array->GetElementAt(0).Get<int32_t>(), 1);
+  DALI_TEST_EQUAL(array->GetElementAt(1).Get<int32_t>(), 2);
+  DALI_TEST_EQUAL(array->GetElementAt(2).Get<int32_t>(), 4);
+  DALI_TEST_EQUAL(array->GetElementAt(3).Get<int32_t>(), 8);
+  DALI_TEST_EQUAL(array->GetElementAt(4).Get<int32_t>(), -500);
+
+  auto propObject = nodeMaps[0].Find("object");
+  DALI_TEST_EQUAL(propObject->GetType(), Property::MAP);
+
+  auto object = propObject->GetMap();
+  DALI_TEST_EQUAL(object->Count(), 5);
+  DALI_TEST_EQUAL(object->Find("physics")->Get<bool>(), true);
+  DALI_TEST_EQUAL(object->Find("elasticity")->Get<float>(), .27f);
+  DALI_TEST_EQUAL(object->Find("drag")->Get<float>(), .91f);
+
+  auto propInnerArray = object->Find("inner array");
+  DALI_TEST_EQUAL(propInnerArray->GetType(), Property::ARRAY);
+
+  auto innerArray = propInnerArray->GetArray();
+  DALI_TEST_EQUAL(innerArray->Count(), 3);
+  DALI_TEST_EQUAL(innerArray->GetElementAt(0).Get<std::string>(), "why");
+  DALI_TEST_EQUAL(innerArray->GetElementAt(1).Get<std::string>(), "not");
+  DALI_TEST_EQUAL(innerArray->GetElementAt(2).Get<bool>(), false);
+
+  auto propInnerObject = object->Find("inner object");
+  DALI_TEST_EQUAL(propInnerObject->GetType(), Property::MAP);
+
+  auto innerObject = propInnerObject->GetMap();
+  DALI_TEST_EQUAL(innerObject->Count(), 1);
+  DALI_TEST_EQUAL(innerObject->Find("supported")->Get<bool>(), true);
+
+  DALI_TEST_EQUAL(nodeMaps[1].Count(), 1u);
+  DALI_TEST_EQUAL(nodeMaps[1].Find("name")->Get<std::string>(), "rootB");
+
+  END_TEST;
+}
+
+int UtcDaliDliLoaderLoadCoverageTest(void)
+{
+  Context ctx;
+
+  auto path = ctx.pathProvider(ResourceType::Mesh) + "coverageTest.dli";
+  DALI_TEST_CHECK(ctx.loader.LoadModel(path, ctx.output));
+  DALI_TEST_CHECK(ctx.errors.empty());
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 1u);
+  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "root");
+
+  DALI_TEST_EQUAL(scene.GetNodeCount(), 1u);
+
+  auto& resources = ctx.resources;
+  DALI_TEST_EQUAL(resources.mMeshes.size(), 1u);
+  DALI_TEST_EQUAL(resources.mShaders.size(), 1u);
+  DALI_TEST_EQUAL(resources.mEnvironmentMaps.size(), 2u);
+  DALI_TEST_EQUAL(resources.mSkeletons.size(), 0u);
+
+  auto& materials = ctx.resources.mMaterials;
+  DALI_TEST_EQUAL(2u, materials.size());
+
+  auto  iMaterial = materials.begin();
+  auto& md        = iMaterial->first;
+  DALI_TEST_EQUAL(md.mTextureStages.size(), 1u);
+
+  auto iTexture = md.mTextureStages.begin();
+  DALI_TEST_CHECK(MaskMatch(iTexture->mSemantic, MaterialDefinition::OCCLUSION));
+  DALI_TEST_EQUAL(iTexture->mTexture.mImageUri, "exercise/Icons/Icon_Idle.png");
+  ++iTexture;
+
+  DALI_TEST_EQUAL(ctx.cameraParameters.size(), 1u);
+  DALI_TEST_EQUAL(ctx.lights.size(), 1u);
+  DALI_TEST_EQUAL(ctx.animations.size(), 0u);
+  DALI_TEST_EQUAL(ctx.animGroups.size(), 0u);
+
+  ViewProjection viewProjection;
+  Transforms     xforms{
+    MatrixStack{},
+    viewProjection};
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+  };
+
+  Customization::Choices choices;
+
+  TestApplication app;
+
+  Actor root = Actor::New();
+  SetActorCentered(root);
+  for(auto iRoot : scene.GetRoots())
+  {
+    auto resourceRefs = resources.CreateRefCounter();
+    scene.CountResourceRefs(iRoot, choices, resourceRefs);
+    resources.mReferenceCounts = std::move(resourceRefs);
+    resources.CountEnvironmentReferences();
+    resources.LoadResources(ctx.pathProvider);
+    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
+    {
+      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
+      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
+      ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
+      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      root.Add(actor);
+    }
+  }
+
+  DALI_TEST_EQUAL(root.GetChildCount(), 1u);
+  DALI_TEST_EQUAL(root.GetChildAt(0).GetProperty(Actor::Property::NAME).Get<std::string>(), "root");
+
+  END_TEST;
+}
diff --git a/automated-tests/src/dali-scene3d-internal/utc-Dali-Gltf2LoaderImpl.cpp b/automated-tests/src/dali-scene3d-internal/utc-Dali-Gltf2LoaderImpl.cpp
new file mode 100644 (file)
index 0000000..8133fb9
--- /dev/null
@@ -0,0 +1,783 @@
+/*
+ * Copyright (c) 2022 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/internal/loader/gltf2-loader-impl.h>
+#include <dali-scene3d/public-api/loader/load-result.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-definition-factory.h>
+#include <dali-test-suite-utils.h>
+#include <string_view>
+
+using namespace Dali;
+using namespace Dali::Scene3D::Loader;
+
+#define DALI_TEST_THROW(expression, exception, predicate) \
+  {                                                       \
+    bool daliTestThrowSuccess__ = false;                  \
+    try                                                   \
+    {                                                     \
+      do                                                  \
+      {                                                   \
+        expression;                                       \
+      } while(0);                                         \
+      printf("No exception was thrown.\n");               \
+    }                                                     \
+    catch(std::decay<exception>::type & ex)               \
+    {                                                     \
+      daliTestThrowSuccess__ = predicate(ex);             \
+    }                                                     \
+    catch(...)                                            \
+    {                                                     \
+      printf("Wrong type of exception thrown.\n");        \
+    }                                                     \
+    DALI_TEST_CHECK(daliTestThrowSuccess__);              \
+  }
+
+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::Internal::Gltf2LoaderImpl 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 UtcDaliGltfLoaderFailedToLoad(void)
+{
+  Context ctx;
+
+  DALI_TEST_EQUAL(ctx.loader.LoadModel("non-existent.gltf", ctx.loadResult), 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());
+
+  END_TEST;
+}
+
+int UtcDaliGltfLoaderFailedToParse(void)
+{
+  Context ctx;
+
+  ShaderDefinitionFactory sdf;
+  sdf.SetResources(ctx.resources);
+
+  DALI_TEST_EQUAL(ctx.loader.LoadModel(TEST_RESOURCE_DIR "/invalid.gltf", ctx.loadResult), 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());
+
+  END_TEST;
+}
+
+int UtcDaliGltfLoaderSuccess1(void)
+{
+  Context ctx;
+
+  LoadSceneMetadata(TEST_RESOURCE_DIR "/AnimatedCube.metadata", ctx.metaData);
+
+  std::unordered_map<std::string, ImageMetadata> imageMetadataGroundTruth;
+  imageMetadataGroundTruth["AnimatedCube_BaseColor.png"]         = ImageMetadata{ImageDimensions(256, 256), Dali::SamplingMode::BOX_THEN_NEAREST};
+  imageMetadataGroundTruth["AnimatedCube_MetallicRoughness.png"] = ImageMetadata{ImageDimensions(256, 256), Dali::SamplingMode::NEAREST};
+
+  auto metaData = ctx.metaData.mImageMetadata.begin();
+  for(auto& groundTruth : imageMetadataGroundTruth)
+  {
+    DALI_TEST_EQUAL(groundTruth.first, metaData->first);
+    DALI_TEST_EQUAL(groundTruth.second.mMinSize, metaData->second.mMinSize);
+    DALI_TEST_EQUAL(groundTruth.second.mSamplingMode, metaData->second.mSamplingMode);
+    ++metaData;
+  }
+
+  ctx.loader.LoadModel(TEST_RESOURCE_DIR "/AnimatedCube.gltf", ctx.loadResult);
+
+  DALI_TEST_EQUAL(1u, ctx.scene.GetRoots().size());
+  DALI_TEST_EQUAL(9u, ctx.scene.GetNodeCount());
+
+  // Default envmap is used
+  DALI_TEST_EQUAL(1u, ctx.resources.mEnvironmentMaps.size());
+
+  TestApplication app;
+
+  Customization::Choices choices;
+  for(auto iRoot : ctx.scene.GetRoots())
+  {
+    auto resourceRefs = ctx.resources.CreateRefCounter();
+    ctx.scene.CountResourceRefs(iRoot, choices, resourceRefs);
+    ctx.resources.mReferenceCounts = std::move(resourceRefs);
+    ctx.resources.CountEnvironmentReferences();
+    ctx.resources.LoadResources(ctx.pathProvider);
+  }
+
+  auto& materials = ctx.resources.mMaterials;
+  DALI_TEST_EQUAL(2u, materials.size());
+  const MaterialDefinition materialGroundTruth[]{
+    {
+      nullptr,
+      MaterialDefinition::ALBEDO | MaterialDefinition::EMISSIVE | MaterialDefinition::OCCLUSION |
+        MaterialDefinition::NORMAL | MaterialDefinition::SPECULAR | MaterialDefinition::SPECULAR_COLOR |
+        (0x80 << MaterialDefinition::ALPHA_CUTOFF_SHIFT),
+      0,
+      Color::WHITE,
+      1.f,
+      0.f,
+      Vector4(1.000, 0.766, 0.336, 1.0),
+      1.f,
+      1.f,
+      Vector3(0.2, 0.1, 0.0),
+      0.0f,
+      0.5f,
+      Vector3(0, 0, 1),
+      true,
+      false,
+      true,
+      false,
+      true,
+      true,
+      {
+        {
+          MaterialDefinition::ALBEDO,
+          {
+            "AnimatedCube_BaseColor.png",
+            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::BOX_THEN_NEAREST,
+          },
+        },
+        {
+          MaterialDefinition::NORMAL,
+          {
+            "AnimatedCube_BaseColor.png",
+            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::BOX_THEN_NEAREST,
+          },
+        },
+        {
+          MaterialDefinition::OCCLUSION,
+          {
+            "AnimatedCube_BaseColor.png",
+            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::BOX_THEN_NEAREST,
+          },
+        },
+        {
+          MaterialDefinition::EMISSIVE,
+          {
+            "AnimatedCube_BaseColor.png",
+            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::BOX_THEN_NEAREST,
+          },
+        },
+        {
+          MaterialDefinition::SPECULAR,
+          {
+            "AnimatedCube_BaseColor.png",
+            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::BOX_THEN_NEAREST,
+          },
+        },
+        {
+          MaterialDefinition::SPECULAR_COLOR,
+          {
+            "AnimatedCube_BaseColor.png",
+            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::BOX_THEN_NEAREST,
+          },
+        },
+      },
+    },
+    {
+      nullptr,
+      MaterialDefinition::ALBEDO | MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS |
+        MaterialDefinition::EMISSIVE | MaterialDefinition::OCCLUSION | MaterialDefinition::NORMAL |
+        MaterialDefinition::GLTF_CHANNELS,
+      0,
+      Color::WHITE,
+      1.f,
+      0.f,
+      Vector4(1.000, 0.766, 0.336, 1.0),
+      1.f,
+      1.f,
+      Vector3(0.2, 0.1, 0.0),
+      0.04f,
+      1.0f,
+      Vector3::ONE,
+      true,
+      true,
+      true,
+      false,
+      true,
+      false,
+      {
+        {
+          MaterialDefinition::ALBEDO,
+          {
+            "AnimatedCube_BaseColor.png",
+            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::BOX_THEN_NEAREST,
+          },
+        },
+        {
+          MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS | MaterialDefinition::GLTF_CHANNELS,
+          {
+            "AnimatedCube_MetallicRoughness.png",
+            SamplerFlags::Encode(FilterMode::NEAREST_MIPMAP_LINEAR, FilterMode::NEAREST, WrapMode::CLAMP_TO_EDGE, WrapMode::MIRRORED_REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::NEAREST,
+          },
+        },
+        {
+          MaterialDefinition::NORMAL,
+          {
+            "AnimatedCube_BaseColor.png",
+            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::BOX_THEN_NEAREST,
+          },
+        },
+        {
+          MaterialDefinition::OCCLUSION,
+          {
+            "AnimatedCube_BaseColor.png",
+            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::BOX_THEN_NEAREST,
+          },
+        },
+        {
+          MaterialDefinition::EMISSIVE,
+          {
+            "AnimatedCube_BaseColor.png",
+            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
+            ImageDimensions(256, 256),
+            SamplingMode::BOX_THEN_NEAREST,
+          },
+        },
+      },
+    },
+  };
+
+  auto iMaterial = materials.begin();
+  auto iMetadata = ctx.metaData.mImageMetadata.begin();
+  for(auto& m : materialGroundTruth)
+  {
+    printf("material %ld\n", iMaterial - materials.begin());
+    auto& md = iMaterial->first;
+    DALI_TEST_EQUAL(md.mFlags, m.mFlags);
+    DALI_TEST_EQUAL(md.mEnvironmentIdx, m.mEnvironmentIdx);
+    DALI_TEST_EQUAL(md.mColor, m.mColor);
+    DALI_TEST_EQUAL(md.mMetallic, m.mMetallic);
+    DALI_TEST_EQUAL(md.mRoughness, m.mRoughness);
+    DALI_TEST_EQUAL(md.mBaseColorFactor, m.mBaseColorFactor);
+    DALI_TEST_EQUAL(md.mNormalScale, m.mNormalScale);
+    DALI_TEST_EQUAL(md.mOcclusionStrength, m.mOcclusionStrength);
+    DALI_TEST_EQUAL(md.mEmissiveFactor, m.mEmissiveFactor);
+    DALI_TEST_EQUAL(md.mDielectricSpecular, m.mDielectricSpecular);
+    DALI_TEST_EQUAL(md.mSpecularFactor, m.mSpecularFactor);
+    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.mNeedNormalTexture, m.mNeedNormalTexture);
+    DALI_TEST_EQUAL(md.mIsOpaque, m.mIsOpaque);
+    DALI_TEST_EQUAL(md.mIsMask, m.mIsMask);
+
+    DALI_TEST_EQUAL(md.mTextureStages.size(), m.mTextureStages.size());
+    auto iTexture = md.mTextureStages.begin();
+    for(auto& ts : m.mTextureStages)
+    {
+      printf("texture %ld\n", iTexture - md.mTextureStages.begin());
+      DALI_TEST_EQUAL(iTexture->mSemantic, ts.mSemantic);
+      DALI_TEST_EQUAL(iTexture->mTexture.mImageUri, ts.mTexture.mImageUri);
+      DALI_TEST_EQUAL(uint32_t(iTexture->mTexture.mSamplerFlags), uint32_t(ts.mTexture.mSamplerFlags)); // don't interpret it as a character
+      DALI_TEST_EQUAL(iTexture->mTexture.mMinImageDimensions, ts.mTexture.mMinImageDimensions);
+      DALI_TEST_EQUAL(iTexture->mTexture.mSamplingMode, ts.mTexture.mSamplingMode);
+
+      ++iTexture;
+    }
+    ++iMaterial;
+    ++iMetadata;
+  }
+
+  auto& meshes = ctx.resources.mMeshes;
+  DALI_TEST_EQUAL(2u, meshes.size());
+
+  using Blob     = MeshDefinition::Blob;
+  using Accessor = MeshDefinition::Accessor;
+  const MeshDefinition meshGroundTruth[]{
+    {
+      nullptr,
+      0,
+      Geometry::TRIANGLES,
+      "AnimatedCube.bin",
+      Accessor{Blob{0, 0}, {}},
+      Accessor{Blob{0, 0}, {}},
+      Accessor{Blob{0, 0}, {}},
+      Accessor{Blob{0, 0}, {}},
+      Accessor{Blob{0, 0}, {}},
+      Accessor{Blob{0, 0}, {}},
+    },
+    {
+      nullptr,
+      0,
+      Geometry::TRIANGLES,
+      "AnimatedCube.bin",
+      Accessor{Blob{0, 0}, {}},
+      Accessor{Blob{0, 0}, {}},
+      Accessor{Blob{0, 0}, {}},
+      Accessor{Blob{0, 0}, {}},
+      Accessor{Blob{0, 0}, {}},
+      Accessor{Blob{0, 0}, {}},
+    },
+  };
+
+  auto iMesh = meshes.begin();
+  for(auto& m : meshGroundTruth)
+  {
+    printf("mesh %ld\n", iMesh - meshes.begin());
+
+    auto& md = iMesh->first;
+    DALI_TEST_EQUAL(md.mFlags, m.mFlags);
+    DALI_TEST_EQUAL(md.mPrimitiveType, m.mPrimitiveType);
+    for(auto mp : {
+          &MeshDefinition::mIndices,
+          &MeshDefinition::mPositions,
+          &MeshDefinition::mNormals,
+          &MeshDefinition::mTexCoords,
+          &MeshDefinition::mColors,
+          &MeshDefinition::mTangents,
+          &MeshDefinition::mJoints0,
+          &MeshDefinition::mWeights0})
+    {
+      DALI_TEST_EQUAL((md.*mp).IsDefined(), (m.*mp).IsDefined());
+      DALI_TEST_EQUAL((md.*mp).mBlob.IsDefined(), (m.*mp).mBlob.IsDefined());
+    }
+
+    DALI_TEST_EQUAL(md.mBlendShapeHeader.IsDefined(), m.mBlendShapeHeader.IsDefined());
+
+    ++iMesh;
+  }
+
+  DALI_TEST_EQUAL(2u, ctx.resources.mShaders.size());
+  DALI_TEST_EQUAL(0u, ctx.resources.mSkeletons.size());
+
+  DALI_TEST_EQUAL(6u, ctx.cameras.size());
+  DALI_TEST_EQUAL(0u, ctx.lights.size());
+  DALI_TEST_EQUAL(1u, ctx.animations.size());
+  DALI_TEST_EQUAL(0u, ctx.animationGroups.size());
+
+  END_TEST;
+}
+
+int UtcDaliGltfLoaderSuccess2(void)
+{
+  Context                 ctx;
+  ShaderDefinitionFactory sdf;
+  sdf.SetResources(ctx.resources);
+
+  ctx.loader.LoadModel(TEST_RESOURCE_DIR "/AnimatedCubeStride.gltf", ctx.loadResult);
+
+  DALI_TEST_EQUAL(1u, ctx.scene.GetRoots().size());
+  DALI_TEST_EQUAL(1u, ctx.scene.GetNodeCount());
+
+  TestApplication app;
+
+  Customization::Choices choices;
+  for(auto iRoot : ctx.scene.GetRoots())
+  {
+    auto resourceRefs = ctx.resources.CreateRefCounter();
+    ctx.scene.CountResourceRefs(iRoot, choices, resourceRefs);
+    ctx.resources.mReferenceCounts = std::move(resourceRefs);
+    ctx.resources.LoadResources(ctx.pathProvider);
+  }
+
+  DALI_TEST_EQUAL(true, ctx.resources.mMeshes[0u].first.mPositions.IsDefined());
+  DALI_TEST_EQUAL(432, ctx.resources.mMeshes[0u].first.mPositions.mBlob.mLength);
+
+  END_TEST;
+}
+
+int UtcDaliGltfLoaderSuccessShort(void)
+{
+  TestApplication app;
+
+  const std::string resourcePath = TEST_RESOURCE_DIR "/";
+  auto              pathProvider = [resourcePath](ResourceType::Value)
+  {
+    return resourcePath;
+  };
+
+  Customization::Choices choices;
+  for(auto modelName : {
+        "2CylinderEngine",
+        "AnimatedMorphCube",
+        "AnimatedMorphSphere",
+        "AnimatedTriangle",
+        "BoxAnimated",
+        "CesiumMan",
+        "CesiumMilkTruck",
+        "EnvironmentTest",
+        "MetalRoughSpheres",
+        "MorphPrimitivesTest",
+        "MRendererTest",
+        "SimpleSparseAccessor",
+        "AnimatedCube",
+      })
+  {
+    Context ctx;
+
+    auto& resources = ctx.resources;
+    resources.mEnvironmentMaps.push_back({});
+
+    printf("%s\n", modelName);
+    ctx.loader.LoadModel(resourcePath + modelName + ".gltf", ctx.loadResult);
+    DALI_TEST_CHECK(ctx.scene.GetNodeCount() > 0);
+
+    auto& scene = ctx.scene;
+    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])
+        {
+          auto raw = resources.mMeshes[i0].first.LoadRaw(resourcePath, resources.mBuffers);
+          DALI_TEST_CHECK(!raw.mAttribs.empty());
+
+          resources.mMeshes[i0].second = resources.mMeshes[i0].first.Load(std::move(raw));
+          DALI_TEST_CHECK(resources.mMeshes[i0].second.geometry);
+        }
+      }
+    }
+  }
+
+  END_TEST;
+}
+
+int UtcDaliGltfLoaderMRendererTest(void)
+{
+  Context ctx;
+
+  ShaderDefinitionFactory sdf;
+  sdf.SetResources(ctx.resources);
+  auto& resources = ctx.resources;
+
+  ctx.loader.LoadModel(TEST_RESOURCE_DIR "/MRendererTest.gltf", ctx.loadResult);
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 1u);
+  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "RootNode");
+  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mScale, Vector3(1.0f, 1.0f, 1.0f));
+
+  DALI_TEST_EQUAL(scene.GetNodeCount(), 1u);
+
+  ViewProjection viewProjection;
+  Transforms     xforms{
+    MatrixStack{},
+    viewProjection};
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+  };
+
+  Customization::Choices choices;
+
+  TestApplication app;
+
+  Actor root = Actor::New();
+  SetActorCentered(root);
+  for(auto iRoot : roots)
+  {
+    auto resourceRefs = resources.CreateRefCounter();
+    scene.CountResourceRefs(iRoot, choices, resourceRefs);
+    ctx.resources.mReferenceCounts = std::move(resourceRefs);
+    ctx.resources.CountEnvironmentReferences();
+    ctx.resources.LoadResources(ctx.pathProvider);
+    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
+    {
+      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
+      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
+      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      root.Add(actor);
+    }
+  }
+
+  DALI_TEST_EQUAL(root.GetChildCount(), 1u);
+  Actor child = root.GetChildAt(0);
+
+  DALI_TEST_EQUAL(child.GetProperty(Actor::Property::NAME).Get<std::string>(), "RootNode");
+  DALI_TEST_EQUAL(child.GetProperty(Actor::Property::SCALE).Get<Vector3>(), Vector3(1.0f, 1.0f, 1.0f));
+  DALI_TEST_EQUAL(child.GetRendererCount(), 1u);
+  DALI_TEST_EQUAL(child.GetRendererAt(0).GetTextures().GetTextureCount(), 4u);
+
+  DALI_TEST_EQUAL(child.GetRendererCount(), 1u);
+  DALI_TEST_EQUAL(child.GetRendererAt(0u).GetProperty<decltype(BlendMode::ON)>(Renderer::Property::BLEND_MODE), BlendMode::ON);
+
+  END_TEST;
+}
+
+int UtcDaliGltfLoaderAnimationLoadingTest(void)
+{
+  Context ctx;
+
+  auto& resources = ctx.resources;
+
+  ctx.loader.LoadModel(TEST_RESOURCE_DIR "/CesiumMan_e.gltf", ctx.loadResult);
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 1u);
+
+  ViewProjection viewProjection;
+  Transforms     xforms{
+    MatrixStack{},
+    viewProjection};
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+  };
+
+  Customization::Choices choices;
+
+  TestApplication app;
+
+  Actor root = Actor::New();
+  SetActorCentered(root);
+  for(auto iRoot : roots)
+  {
+    auto resourceRefs = resources.CreateRefCounter();
+    scene.CountResourceRefs(iRoot, choices, resourceRefs);
+    resources.mReferenceCounts = std::move(resourceRefs);
+    resources.CountEnvironmentReferences();
+    resources.LoadResources(ctx.pathProvider);
+    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
+    {
+      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
+      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
+      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      root.Add(actor);
+    }
+  }
+
+  DALI_TEST_EQUAL(ctx.loadResult.mAnimationDefinitions.size(), 1u);
+  DALI_TEST_EQUAL(ctx.loadResult.mAnimationDefinitions[0].mProperties.size(), 57u);
+
+  uint32_t id = ctx.loadResult.mScene.GetNode(ctx.loadResult.mAnimationDefinitions[0].mProperties[0].mNodeIndex)->mNodeId;
+  DALI_TEST_EQUAL(id, root.FindChildByName("Skeleton_torso_joint_1").GetProperty<int32_t>(Dali::Actor::Property::ID));
+
+  END_TEST;
+}
+
+int UtcDaliGltfLoaderImageFromBufferView(void)
+{
+  Context ctx;
+
+  ShaderDefinitionFactory sdf;
+  sdf.SetResources(ctx.resources);
+  auto& resources = ctx.resources;
+
+  ctx.loader.LoadModel(TEST_RESOURCE_DIR "/EnvironmentTest_b.gltf", ctx.loadResult);
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 1u);
+
+  ViewProjection viewProjection;
+  Transforms     xforms{
+    MatrixStack{},
+    viewProjection};
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+  };
+
+  Customization::Choices choices;
+
+  TestApplication app;
+
+  Actor root = Actor::New();
+  SetActorCentered(root);
+  for(auto iRoot : roots)
+  {
+    auto resourceRefs = resources.CreateRefCounter();
+    scene.CountResourceRefs(iRoot, choices, resourceRefs);
+    resources.mReferenceCounts = std::move(resourceRefs);
+    resources.CountEnvironmentReferences();
+    resources.LoadResources(ctx.pathProvider);
+    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
+    {
+      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
+      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
+      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      root.Add(actor);
+    }
+  }
+
+  DALI_TEST_CHECK(resources.mMaterials[0].second.GetTextureCount() > 1);
+  DALI_TEST_EQUAL(resources.mMaterials[0].second.GetTexture(0).GetWidth(), 256);
+  DALI_TEST_EQUAL(resources.mMaterials[0].second.GetTexture(0).GetHeight(), 256);
+
+  END_TEST;
+}
+
+int UtcDaliGltfLoaderUint8Indices(void)
+{
+  Context ctx;
+
+  auto& resources = ctx.resources;
+
+  ctx.loader.LoadModel(TEST_RESOURCE_DIR "/AlphaBlendModeTest.gltf", ctx.loadResult);
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+  DALI_TEST_EQUAL(roots.size(), 1u);
+
+  ViewProjection viewProjection;
+  Transforms     xforms{
+    MatrixStack{},
+    viewProjection};
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+  };
+
+  Customization::Choices choices;
+
+  TestApplication app;
+
+  Actor root = Actor::New();
+  SetActorCentered(root);
+  for(auto iRoot : roots)
+  {
+    auto resourceRefs = resources.CreateRefCounter();
+    scene.CountResourceRefs(iRoot, choices, resourceRefs);
+    resources.mReferenceCounts = std::move(resourceRefs);
+    resources.CountEnvironmentReferences();
+    resources.LoadResources(ctx.pathProvider);
+    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
+    {
+      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
+      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
+      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      root.Add(actor);
+    }
+  }
+
+  DALI_TEST_CHECK(root.FindChildByName("Bed"));
+  DALI_TEST_CHECK(root.FindChildByName("DecalBlend"));
+  DALI_TEST_CHECK(root.FindChildByName("DecalOpaque"));
+
+  END_TEST;
+}
index 1f80defaf2fd57b4de47c80208eb252335bd6359..799dd6db30478746c2ae80eaafc35e2412350b73 100755 (executable)
@@ -13,10 +13,8 @@ SET(TC_SOURCES
   utc-Dali-BvhLoader.cpp
   utc-Dali-CameraParameters.cpp
   utc-Dali-EnvironmentMapLoader.cpp
-  utc-Dali-DliLoader.cpp
   utc-Dali-EnvironmentDefinition.cpp
   utc-Dali-FacialAnimation.cpp
-  utc-Dali-Gltf2Loader.cpp
   utc-Dali-KtxLoader.cpp
   utc-Dali-Model.cpp
   utc-Dali-SceneView.cpp
diff --git a/automated-tests/src/dali-scene3d/utc-Dali-DliLoader.cpp b/automated-tests/src/dali-scene3d/utc-Dali-DliLoader.cpp
deleted file mode 100644 (file)
index 9d67ab4..0000000
+++ /dev/null
@@ -1,707 +0,0 @@
-/*
- * Copyright (c) 2022 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/internal/loader/json-util.h>
-#include <dali-scene3d/public-api/loader/dli-loader.h>
-#include <dali-scene3d/public-api/loader/load-result.h>
-#include <dali-scene3d/public-api/loader/resource-bundle.h>
-#include <dali-scene3d/public-api/loader/scene-definition.h>
-#include <dali-test-suite-utils.h>
-#include <string_view>
-
-using namespace Dali;
-using namespace Dali::Scene3D::Loader;
-
-namespace
-{
-void ConfigureBlendShapeShaders(ResourceBundle& resources, const SceneDefinition& scene, Actor root, std::vector<BlendshapeShaderConfigurationRequest>&& requests)
-{
-  std::vector<std::string> errors;
-  auto                     onError = [&errors](const std::string& msg) {
-    errors.push_back(msg);
-  };
-
-  if(!scene.ConfigureBlendshapeShaders(resources, root, std::move(requests), onError))
-  {
-    ExceptionFlinger flinger(ASSERT_LOCATION);
-    for(auto& msg : errors)
-    {
-      flinger << msg << '\n';
-    }
-  }
-}
-
-struct Context
-{
-  ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type) {
-    return TEST_RESOURCE_DIR "/";
-  };
-
-  ResourceBundle                        resources;
-  SceneDefinition                       scene;
-  std::vector<CameraParameters>         cameraParameters;
-  std::vector<LightParameters>          lights;
-  std::vector<AnimationDefinition>      animations;
-  std::vector<AnimationGroupDefinition> animGroups;
-
-  SceneMetadata metaData;
-
-  LoadResult output{
-    resources,
-    scene,
-    metaData,
-    animations,
-    animGroups,
-    cameraParameters,
-    lights};
-
-  DliLoader::InputParams input{
-    pathProvider(ResourceType::Mesh),
-    nullptr,
-    {},
-    {},
-    nullptr,
-  };
-  DliLoader::LoadParams loadParams{input, output};
-
-  std::vector<std::string> errors;
-  DliLoader                loader;
-
-  StringCallback onError = [this](const std::string& error) {
-    errors.push_back(error);
-    printf("%s\n", error.c_str());
-  };
-
-  Context()
-  {
-    loader.SetErrorCallback(onError);
-  }
-};
-
-bool StringHasTokens(const char* string, const std::vector<const char*>& tokens)
-{
-  for(auto& token : tokens)
-  {
-    auto result = strstr(string, token);
-    if(nullptr == result)
-    {
-      return false;
-    }
-    string = result + strlen(token);
-  }
-  return true;
-}
-
-} // namespace
-
-int UtcDaliDliLoaderLoadSceneNotFound(void)
-{
-  Context ctx;
-
-  DALI_TEST_EQUAL(ctx.loader.LoadScene("does_not_exist.dli", ctx.loadParams), false);
-
-  auto error = ctx.loader.GetParseError();
-  DALI_TEST_CHECK(StringHasTokens(error.c_str(), {"Empty source buffer to parse."}));
-
-  END_TEST;
-}
-
-int UtcDaliDliLoaderLoadSceneFailParse(void)
-{
-  Context ctx;
-
-  auto path = ctx.pathProvider(ResourceType::Mesh) + "invalid.gltf";
-  DALI_TEST_EQUAL(ctx.loader.LoadScene(path, ctx.loadParams), false);
-
-  auto error = ctx.loader.GetParseError();
-  DALI_TEST_CHECK(StringHasTokens(error.c_str(), {"Unexpected character."}));
-
-  END_TEST;
-}
-
-int UtcDaliDliLoaderLoadSceneAssertions(void)
-{
-  const std::pair<std::string, std::string> pathExceptionPairs[]{
-    // from RequireChild()
-    {"scenes-nodes-missing", "Failed to find child node"},
-    {"scenes-missing", "Failed to find child node"},
-    {"nodes-missing", "Failed to find child node"},
-    // from ParseSceneInternal()
-    {"scene-out-of-bounds", "out of bounds"},
-    {"nodes-invalid-type", "invalid type; array required"},
-    {"nodes-array-empty", "must define a node id"},
-    {"root-id-invalid", "invalid value for root node index"},
-    {"root-id-out-of-bounds", "out of bounds"},
-    {"root-node-invalid-type", "invalid JSON type; object required"},
-    // from ParseSkeletons()
-    {"skeleton-node-missing", "Missing required attribute"},
-    {"skeleton-root-not-found", "not defined"},
-    // from ParseShaders()
-    {"shader-vertex-missing", "Missing vertex / fragment shader"},
-    {"shader-fragment-missing", "Missing vertex / fragment shader"},
-    // from ParseMeshes()
-    {"mesh-uri-missing", "Missing required attribute"},
-    {"mesh-indices-read-fail", "Failed to read indices"},
-    {"mesh-positions-read-fail", "Failed to read positions"},
-    // from ParseMaterials()
-    {"material-environment-out-of-bounds", "out of bounds"},
-    // from ParseNodes()
-    {"node-model-mesh-missing", "Missing mesh"},
-    {"node-arc-mesh-missing", "Missing mesh"},
-    {"node-animated-image-mesh-missing", "Missing mesh"},
-    {"node-renderable-mesh-invalid-type", "Invalid Mesh index type"},
-    {"node-renderable-mesh-out-of-bounds", "out of bounds"},
-    {"node-child-invalid-type", "invalid index type"},
-    // from ParseAnimations()
-    {"animation-failed-to-open", "Failed to open animation data"}};
-  for(auto& i : pathExceptionPairs)
-  {
-    Context ctx;
-
-    auto path = ctx.pathProvider(ResourceType::Mesh) + "dli/" + i.first + ".dli";
-    printf("\n\n%s: %s\n", path.c_str(), i.second.c_str());
-    DALI_TEST_ASSERTION(ctx.loader.LoadScene(path, ctx.loadParams), i.second.c_str());
-  }
-
-  END_TEST;
-}
-
-int UtcDaliDliLoaderLoadSceneExercise(void)
-{
-  Context ctx;
-
-  auto path = ctx.pathProvider(ResourceType::Mesh) + "exercise.dli";
-  DALI_TEST_CHECK(ctx.loader.LoadScene(path, ctx.loadParams));
-  DALI_TEST_CHECK(ctx.errors.empty());
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 2u);
-  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "Backdrop"); // default scene is scene 1 - this one.
-  DALI_TEST_EQUAL(scene.GetNode(roots[1])->mName, "ExerciseDemo");
-
-  DALI_TEST_EQUAL(scene.GetNodeCount(), 96u);
-
-  auto& resources = ctx.resources;
-  DALI_TEST_EQUAL(resources.mMeshes.size(), 11u);
-  DALI_TEST_EQUAL(resources.mMaterials.size(), 13u);
-  DALI_TEST_EQUAL(resources.mShaders.size(), 5u);
-  DALI_TEST_EQUAL(resources.mEnvironmentMaps.size(), 2u);
-  DALI_TEST_EQUAL(resources.mSkeletons.size(), 1u);
-
-  DALI_TEST_EQUAL(ctx.cameraParameters.size(), 1u);
-  DALI_TEST_EQUAL(ctx.lights.size(), 1u);
-  DALI_TEST_EQUAL(ctx.animations.size(), 18u);
-  DALI_TEST_EQUAL(ctx.animGroups.size(), 16u);
-
-  ViewProjection viewProjection;
-  Transforms     xforms{
-    MatrixStack{},
-    viewProjection};
-  NodeDefinition::CreateParams nodeParams{
-    resources,
-    xforms,
-  };
-
-  Customization::Choices choices;
-
-  TestApplication app;
-
-  Actor root = Actor::New();
-  SetActorCentered(root);
-  for(auto iRoot : scene.GetRoots())
-  {
-    auto resourceRefs = resources.CreateRefCounter();
-    scene.CountResourceRefs(iRoot, choices, resourceRefs);
-    resources.CountEnvironmentReferences(resourceRefs);
-    resources.LoadResources(resourceRefs, ctx.pathProvider);
-    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
-    {
-      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
-      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
-      ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
-      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
-      root.Add(actor);
-    }
-  }
-
-  DALI_TEST_EQUAL(root.GetChildCount(), 2u);
-  DALI_TEST_EQUAL(root.GetChildAt(0).GetProperty(Actor::Property::NAME).Get<std::string>(), "Backdrop");
-  DALI_TEST_EQUAL(root.GetChildAt(1).GetProperty(Actor::Property::NAME).Get<std::string>(), "ExerciseDemo");
-
-  END_TEST;
-}
-
-int UtcDaliDliLoaderLoadSceneMorph(void)
-{
-  Context ctx;
-
-  std::vector<std::string> metadata;
-  uint32_t                 metadataCount = 0;
-  ctx.input.mPreNodeCategoryProcessors.push_back({"metadata",
-                                                  [&](const Property::Array& array, StringCallback) {
-                                                    std::string key, value;
-                                                    for(uint32_t i0 = 0, i1 = array.Count(); i0 < i1; ++i0)
-                                                    {
-                                                      auto& data = array.GetElementAt(i0);
-                                                      DALI_TEST_EQUAL(data.GetType(), Property::MAP);
-
-                                                      auto map   = data.GetMap();
-                                                      auto key   = map->Find("key");
-                                                      auto value = map->Find("value");
-                                                      DALI_TEST_EQUAL(key->GetType(), Property::STRING);
-                                                      DALI_TEST_EQUAL(value->GetType(), Property::STRING);
-                                                      metadata.push_back(key->Get<std::string>() + ":" + value->Get<std::string>());
-
-                                                      ++metadataCount;
-                                                    }
-                                                  }});
-
-  std::vector<std::string> behaviors;
-  uint32_t                 behaviorCount = 0;
-  ctx.input.mPostNodeCategoryProcessors.push_back({"behaviors",
-                                                   [&](const Property::Array& array, StringCallback) {
-                                                     for(uint32_t i0 = 0, i1 = array.Count(); i0 < i1; ++i0)
-                                                     {
-                                                       auto& data = array.GetElementAt(i0);
-                                                       DALI_TEST_EQUAL(data.GetType(), Property::MAP);
-
-                                                       auto map   = data.GetMap();
-                                                       auto event = map->Find("event");
-                                                       auto url   = map->Find("url");
-                                                       DALI_TEST_EQUAL(event->GetType(), Property::STRING);
-                                                       DALI_TEST_EQUAL(url->GetType(), Property::STRING);
-                                                       behaviors.push_back(event->Get<std::string>() + ":" + url->Get<std::string>());
-
-                                                       ++behaviorCount;
-                                                     }
-                                                   }});
-
-  size_t numNodes                  = 0;
-  ctx.input.mNodePropertyProcessor = [&](const NodeDefinition&, const Property::Map&, StringCallback) {
-    ++numNodes;
-  };
-
-  auto path = ctx.pathProvider(ResourceType::Mesh) + "morph.dli";
-  DALI_TEST_CHECK(ctx.loader.LoadScene(path, ctx.loadParams));
-  DALI_TEST_CHECK(ctx.errors.empty());
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 1u);
-  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "HeadTest_002");
-
-  DALI_TEST_EQUAL(numNodes, 3u);
-  DALI_TEST_EQUAL(scene.GetNodeCount(), numNodes);
-
-  auto& resources = ctx.resources;
-  DALI_TEST_EQUAL(resources.mMeshes.size(), 2u);
-  DALI_TEST_EQUAL(resources.mMaterials.size(), 1u);
-  DALI_TEST_EQUAL(resources.mShaders.size(), 5u);
-  DALI_TEST_EQUAL(resources.mEnvironmentMaps.size(), 2u);
-  DALI_TEST_EQUAL(resources.mSkeletons.size(), 0u);
-
-  DALI_TEST_EQUAL(ctx.cameraParameters.size(), 1u);
-  DALI_TEST_EQUAL(ctx.lights.size(), 1u);
-  DALI_TEST_EQUAL(ctx.animations.size(), 1u);
-  DALI_TEST_EQUAL(ctx.animGroups.size(), 0u);
-
-  DALI_TEST_EQUAL(metadata.size(), 4u);
-  DALI_TEST_EQUAL(behaviors.size(), 1u);
-
-  ViewProjection viewProjection;
-  Transforms     xforms{
-    MatrixStack{},
-    viewProjection};
-  NodeDefinition::CreateParams nodeParams{
-    resources,
-    xforms,
-  };
-
-  Customization::Choices choices;
-
-  TestApplication app;
-
-  Actor root = Actor::New();
-  SetActorCentered(root);
-  for(auto iRoot : scene.GetRoots())
-  {
-    auto resourceRefs = resources.CreateRefCounter();
-    scene.CountResourceRefs(iRoot, choices, resourceRefs);
-    resources.CountEnvironmentReferences(resourceRefs);
-    resources.LoadResources(resourceRefs, ctx.pathProvider);
-    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
-    {
-      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
-      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
-      ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
-      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
-      root.Add(actor);
-    }
-  }
-
-  DALI_TEST_EQUAL(root.GetChildCount(), 1u);
-  DALI_TEST_EQUAL(root.GetChildAt(0).GetProperty(Actor::Property::NAME).Get<std::string>(), "HeadTest_002");
-
-  END_TEST;
-}
-
-int UtcDaliDliLoaderLoadSceneArc(void)
-{
-  Context ctx;
-
-  auto path = ctx.pathProvider(ResourceType::Mesh) + "arc.dli";
-  DALI_TEST_CHECK(ctx.loader.LoadScene(path, ctx.loadParams));
-  DALI_TEST_CHECK(ctx.errors.empty());
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 1u);
-  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "root");
-
-  DALI_TEST_EQUAL(scene.GetNodeCount(), 2u);
-
-  auto& resources = ctx.resources;
-  DALI_TEST_EQUAL(resources.mMeshes.size(), 1u);
-  DALI_TEST_EQUAL(resources.mMaterials.size(), 1u);
-  DALI_TEST_EQUAL(resources.mShaders.size(), 1u);
-  DALI_TEST_EQUAL(resources.mEnvironmentMaps.size(), 1u);
-  DALI_TEST_EQUAL(resources.mSkeletons.size(), 0u);
-
-  DALI_TEST_EQUAL(ctx.cameraParameters.size(), 0u);
-  DALI_TEST_EQUAL(ctx.lights.size(), 0u);
-  DALI_TEST_EQUAL(ctx.animations.size(), 0u);
-  DALI_TEST_EQUAL(ctx.animGroups.size(), 0u);
-
-  ViewProjection viewProjection;
-  Transforms     xforms{
-    MatrixStack{},
-    viewProjection};
-  NodeDefinition::CreateParams nodeParams{
-    resources,
-    xforms,
-  };
-
-  Customization::Choices choices;
-
-  TestApplication app;
-
-  Actor root = Actor::New();
-  SetActorCentered(root);
-  for(auto iRoot : scene.GetRoots())
-  {
-    auto resourceRefs = resources.CreateRefCounter();
-    scene.CountResourceRefs(iRoot, choices, resourceRefs);
-    resources.CountEnvironmentReferences(resourceRefs);
-    resources.LoadResources(resourceRefs, ctx.pathProvider);
-    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
-    {
-      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
-      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
-      ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
-      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
-      root.Add(actor);
-    }
-  }
-
-  DALI_TEST_EQUAL(root.GetChildCount(), 1u);
-  DALI_TEST_EQUAL(root.GetChildAt(0).GetProperty(Actor::Property::NAME).Get<std::string>(), "root");
-
-  END_TEST;
-}
-
-int UtcDaliDliLoaderLoadSceneShaderUniforms(void)
-{
-  Context ctx;
-
-  auto path = ctx.pathProvider(ResourceType::Mesh) + "dli/shader-uniforms.dli";
-  DALI_TEST_CHECK(ctx.loader.LoadScene(path, ctx.loadParams));
-  DALI_TEST_EQUAL(ctx.errors.size(), 1u);
-  DALI_TEST_CHECK(ctx.errors[0].find("failed to infer type") != std::string::npos);
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 1u);
-  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "root");
-
-  DALI_TEST_EQUAL(scene.GetNodeCount(), 1u);
-
-  auto& resources = ctx.resources;
-  DALI_TEST_EQUAL(resources.mMeshes.size(), 0u);
-  DALI_TEST_EQUAL(resources.mMaterials.size(), 0u);
-  DALI_TEST_EQUAL(resources.mShaders.size(), 1u);
-  DALI_TEST_EQUAL(resources.mEnvironmentMaps.size(), 0u);
-  DALI_TEST_EQUAL(resources.mSkeletons.size(), 0u);
-
-  auto raw = resources.mShaders[0].first.LoadRaw(ctx.pathProvider(ResourceType::Shader));
-
-  TestApplication app;
-
-  auto shader = resources.mShaders[0].first.Load(std::move(raw));
-  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uBool")).Get<float>(), 1.0f);
-  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uInt")).Get<float>(), 255.0f);
-  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uFloat")).Get<float>(), -0.5f);
-  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uVec2")).Get<Vector2>(), Vector2(100.0f, -100.0f));
-  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uVec3")).Get<Vector3>(), Vector3(50.0f, 0.f, -200.0f));
-  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uVec4")).Get<Vector4>(), Vector4(0.1774f, 1.0f, 0.5333f, 0.7997f));
-  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uMat3")).Get<Matrix3>(), Matrix3(9.0f, 8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f));
-
-  Matrix expectedMatrix;
-  expectedMatrix.SetTransformComponents(Vector3::ONE * 8.0, Quaternion::IDENTITY, Vector3::ZERO);
-  DALI_TEST_EQUAL(shader.GetProperty(shader.GetPropertyIndex("uMat4")).Get<Matrix>(), expectedMatrix);
-
-  END_TEST;
-}
-
-int UtcDaliDliLoaderLoadSceneExtras(void)
-{
-  Context ctx;
-
-  auto path = ctx.pathProvider(ResourceType::Mesh) + "dli/extras.dli";
-  DALI_TEST_CHECK(ctx.loader.LoadScene(path, ctx.loadParams));
-  DALI_TEST_EQUAL(ctx.errors.size(), 3u);
-  DALI_TEST_CHECK(ctx.errors[0].find("already defined; overriding") != std::string::npos);
-  DALI_TEST_CHECK(ctx.errors[1].find("empty string is invalid for name") != std::string::npos);
-  DALI_TEST_CHECK(ctx.errors[2].find("failed to interpret value") != std::string::npos);
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 1u);
-  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "root");
-
-  DALI_TEST_EQUAL(scene.GetNodeCount(), 1u);
-
-  ViewProjection viewProjection;
-  Transforms     xforms{
-    MatrixStack{},
-    viewProjection};
-  auto&                        resources = ctx.resources;
-  NodeDefinition::CreateParams nodeParams{
-    resources,
-    xforms,
-  };
-
-  Customization::Choices choices;
-
-  TestApplication app;
-  Actor           actor = scene.CreateNodes(0, choices, nodeParams);
-
-  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("fudgeFactor")).Get<float>(), 9000.1f);
-  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("fudgeVector")).Get<Vector2>(), Vector2(-.25f, 17.f));
-  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("isThisTheRealLife")).Get<bool>(), true);
-  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("isThisJustFantasy")).Get<bool>(), false);
-  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("velocity")).Get<Vector3>(), Vector3(.1f, 58.f, -.2f));
-  DALI_TEST_EQUAL(actor.GetProperty(actor.GetPropertyIndex("frameOfReference")).Get<Matrix>(), Matrix::IDENTITY);
-
-  END_TEST;
-}
-
-int UtcDaliDliLoaderLoadSceneConstraints(void)
-{
-  Context ctx;
-
-  auto path = ctx.pathProvider(ResourceType::Mesh) + "dli/constraints.dli";
-  DALI_TEST_CHECK(ctx.loader.LoadScene(path, ctx.loadParams));
-  DALI_TEST_EQUAL(ctx.errors.size(), 1u);
-  DALI_TEST_CHECK(ctx.errors[0].find("invalid", ctx.errors[0].find("node ID")) != std::string::npos);
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 1u);
-  DALI_TEST_EQUAL(scene.GetNode(0)->mName, "root");
-  DALI_TEST_EQUAL(scene.GetNode(1)->mName, "Alice");
-  DALI_TEST_EQUAL(scene.GetNode(2)->mName, "Bob");
-  DALI_TEST_EQUAL(scene.GetNode(3)->mName, "Charlie");
-
-  DALI_TEST_EQUAL(scene.GetNodeCount(), 4u);
-
-  ViewProjection viewProjection;
-  Transforms     xforms{
-    MatrixStack{},
-    viewProjection};
-  auto&                        resources = ctx.resources;
-  NodeDefinition::CreateParams nodeParams{
-    resources,
-    xforms,
-  };
-
-  Customization::Choices choices;
-
-  TestApplication app;
-
-  Actor root    = scene.CreateNodes(0, choices, nodeParams);
-  Actor alice   = root.FindChildByName("Alice");
-  Actor bob     = root.FindChildByName("Bob");
-  Actor charlie = root.FindChildByName("Charlie");
-
-  DALI_TEST_EQUAL(nodeParams.mConstrainables.size(), 3u);
-  DALI_TEST_EQUAL(bob.GetProperty(bob.GetPropertyIndex("angularVelocity")).Get<Vector2>(), Vector2(-0.5, 0.0004));
-
-  ctx.errors.clear();
-  scene.ApplyConstraints(root, std::move(nodeParams.mConstrainables), ctx.onError);
-  DALI_TEST_CHECK(ctx.errors.empty());
-
-  app.GetScene().Add(root);
-  app.SendNotification();
-  app.Render();
-  app.SendNotification();
-  app.Render();
-
-  DALI_TEST_EQUAL(charlie.GetCurrentProperty(Actor::Property::ORIENTATION), alice.GetProperty(Actor::Property::ORIENTATION));
-  DALI_TEST_EQUAL(charlie.GetCurrentProperty(Actor::Property::POSITION), bob.GetProperty(Actor::Property::POSITION));
-  DALI_TEST_EQUAL(charlie.GetCurrentProperty(charlie.GetPropertyIndex("angularVelocity")), bob.GetProperty(bob.GetPropertyIndex("angularVelocity")));
-
-  END_TEST;
-}
-
-int UtcDaliDliLoaderNodeProcessor(void)
-{
-  Context ctx;
-
-  std::vector<Property::Map> nodeMaps;
-  ctx.input.mNodePropertyProcessor = [&](const NodeDefinition&, Property::Map&& map, StringCallback) {
-    nodeMaps.push_back(map);
-  };
-
-  auto path = ctx.pathProvider(ResourceType::Mesh) + "dli/node-processor.dli";
-  DALI_TEST_CHECK(ctx.loader.LoadScene(path, ctx.loadParams));
-
-  DALI_TEST_EQUAL(nodeMaps.size(), 2u);
-  DALI_TEST_EQUAL(nodeMaps[0].Count(), 5u);
-  DALI_TEST_EQUAL(nodeMaps[0].Find("name")->Get<std::string>(), "rootA");
-  DALI_TEST_EQUAL(nodeMaps[0].Find("nickname")->Get<std::string>(), "same as name");
-  DALI_TEST_EQUAL(nodeMaps[0].Find("favourite number")->Get<int32_t>(), 63478);
-
-  auto propArray = nodeMaps[0].Find("array");
-  DALI_TEST_EQUAL(propArray->GetType(), Property::ARRAY);
-
-  auto array = propArray->GetArray();
-  DALI_TEST_EQUAL(array->Count(), 5);
-  DALI_TEST_EQUAL(array->GetElementAt(0).Get<int32_t>(), 1);
-  DALI_TEST_EQUAL(array->GetElementAt(1).Get<int32_t>(), 2);
-  DALI_TEST_EQUAL(array->GetElementAt(2).Get<int32_t>(), 4);
-  DALI_TEST_EQUAL(array->GetElementAt(3).Get<int32_t>(), 8);
-  DALI_TEST_EQUAL(array->GetElementAt(4).Get<int32_t>(), -500);
-
-  auto propObject = nodeMaps[0].Find("object");
-  DALI_TEST_EQUAL(propObject->GetType(), Property::MAP);
-
-  auto object = propObject->GetMap();
-  DALI_TEST_EQUAL(object->Count(), 5);
-  DALI_TEST_EQUAL(object->Find("physics")->Get<bool>(), true);
-  DALI_TEST_EQUAL(object->Find("elasticity")->Get<float>(), .27f);
-  DALI_TEST_EQUAL(object->Find("drag")->Get<float>(), .91f);
-
-  auto propInnerArray = object->Find("inner array");
-  DALI_TEST_EQUAL(propInnerArray->GetType(), Property::ARRAY);
-
-  auto innerArray = propInnerArray->GetArray();
-  DALI_TEST_EQUAL(innerArray->Count(), 3);
-  DALI_TEST_EQUAL(innerArray->GetElementAt(0).Get<std::string>(), "why");
-  DALI_TEST_EQUAL(innerArray->GetElementAt(1).Get<std::string>(), "not");
-  DALI_TEST_EQUAL(innerArray->GetElementAt(2).Get<bool>(), false);
-
-  auto propInnerObject = object->Find("inner object");
-  DALI_TEST_EQUAL(propInnerObject->GetType(), Property::MAP);
-
-  auto innerObject = propInnerObject->GetMap();
-  DALI_TEST_EQUAL(innerObject->Count(), 1);
-  DALI_TEST_EQUAL(innerObject->Find("supported")->Get<bool>(), true);
-
-  DALI_TEST_EQUAL(nodeMaps[1].Count(), 1u);
-  DALI_TEST_EQUAL(nodeMaps[1].Find("name")->Get<std::string>(), "rootB");
-
-  END_TEST;
-}
-
-int UtcDaliDliLoaderLoadCoverageTest(void)
-{
-  Context ctx;
-
-  auto path = ctx.pathProvider(ResourceType::Mesh) + "coverageTest.dli";
-  DALI_TEST_CHECK(ctx.loader.LoadScene(path, ctx.loadParams));
-  DALI_TEST_CHECK(ctx.errors.empty());
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 1u);
-  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "root");
-
-  DALI_TEST_EQUAL(scene.GetNodeCount(), 1u);
-
-  auto& resources = ctx.resources;
-  DALI_TEST_EQUAL(resources.mMeshes.size(), 1u);
-  DALI_TEST_EQUAL(resources.mShaders.size(), 1u);
-  DALI_TEST_EQUAL(resources.mEnvironmentMaps.size(), 2u);
-  DALI_TEST_EQUAL(resources.mSkeletons.size(), 0u);
-
-  auto& materials = ctx.resources.mMaterials;
-  DALI_TEST_EQUAL(2u, materials.size());
-
-  auto  iMaterial = materials.begin();
-  auto& md        = iMaterial->first;
-  DALI_TEST_EQUAL(md.mTextureStages.size(), 1u);
-
-  auto iTexture = md.mTextureStages.begin();
-  DALI_TEST_CHECK(MaskMatch(iTexture->mSemantic, MaterialDefinition::OCCLUSION));
-  DALI_TEST_EQUAL(iTexture->mTexture.mImageUri, "exercise/Icons/Icon_Idle.png");
-  ++iTexture;
-
-  DALI_TEST_EQUAL(ctx.cameraParameters.size(), 1u);
-  DALI_TEST_EQUAL(ctx.lights.size(), 1u);
-  DALI_TEST_EQUAL(ctx.animations.size(), 0u);
-  DALI_TEST_EQUAL(ctx.animGroups.size(), 0u);
-
-  ViewProjection viewProjection;
-  Transforms     xforms{
-    MatrixStack{},
-    viewProjection};
-  NodeDefinition::CreateParams nodeParams{
-    resources,
-    xforms,
-  };
-
-  Customization::Choices choices;
-
-  TestApplication app;
-
-  Actor root = Actor::New();
-  SetActorCentered(root);
-  for(auto iRoot : scene.GetRoots())
-  {
-    auto resourceRefs = resources.CreateRefCounter();
-    scene.CountResourceRefs(iRoot, choices, resourceRefs);
-    resources.CountEnvironmentReferences(resourceRefs);
-    resources.LoadResources(resourceRefs, ctx.pathProvider);
-    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
-    {
-      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
-      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
-      ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
-      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
-      root.Add(actor);
-    }
-  }
-
-  DALI_TEST_EQUAL(root.GetChildCount(), 1u);
-  DALI_TEST_EQUAL(root.GetChildAt(0).GetProperty(Actor::Property::NAME).Get<std::string>(), "root");
-
-  END_TEST;
-}
diff --git a/automated-tests/src/dali-scene3d/utc-Dali-Gltf2Loader.cpp b/automated-tests/src/dali-scene3d/utc-Dali-Gltf2Loader.cpp
deleted file mode 100644 (file)
index e788a5f..0000000
+++ /dev/null
@@ -1,802 +0,0 @@
-/*
- * Copyright (c) 2022 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/gltf2-loader.h>
-#include <dali-scene3d/public-api/loader/load-result.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-definition-factory.h>
-#include <dali-test-suite-utils.h>
-#include <string_view>
-
-using namespace Dali;
-using namespace Dali::Scene3D::Loader;
-
-#define DALI_TEST_THROW(expression, exception, predicate) \
-  {                                                       \
-    bool daliTestThrowSuccess__ = false;                  \
-    try                                                   \
-    {                                                     \
-      do                                                  \
-      {                                                   \
-        expression;                                       \
-      } while(0);                                         \
-      printf("No exception was thrown.\n");               \
-    }                                                     \
-    catch(std::decay<exception>::type & ex)               \
-    {                                                     \
-      daliTestThrowSuccess__ = predicate(ex);             \
-    }                                                     \
-    catch(...)                                            \
-    {                                                     \
-      printf("Wrong type of exception thrown.\n");        \
-    }                                                     \
-    DALI_TEST_CHECK(daliTestThrowSuccess__);              \
-  }
-
-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};
-};
-
-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 UtcDaliGltfLoaderFailedToLoad(void)
-{
-  Context ctx;
-
-  ShaderDefinitionFactory sdf;
-  sdf.SetResources(ctx.resources);
-
-  InitializeGltfLoader();
-  DALI_TEST_THROW(LoadGltfScene("non-existent.gltf", sdf, ctx.loadResult),
-                  std::runtime_error,
-                  ExceptionMessageStartsWith{"Failed to load"});
-
-  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());
-
-  END_TEST;
-}
-
-int UtcDaliGltfLoaderFailedToParse(void)
-{
-  Context ctx;
-
-  ShaderDefinitionFactory sdf;
-  sdf.SetResources(ctx.resources);
-
-  InitializeGltfLoader();
-  DALI_TEST_THROW(LoadGltfScene(TEST_RESOURCE_DIR "/invalid.gltf", sdf, ctx.loadResult),
-                  std::runtime_error,
-                  ExceptionMessageStartsWith{"Failed to parse"});
-
-  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());
-
-  END_TEST;
-}
-
-int UtcDaliGltfLoaderSuccess1(void)
-{
-  Context ctx;
-
-  LoadSceneMetadata(TEST_RESOURCE_DIR "/AnimatedCube.metadata", ctx.metaData);
-
-  std::unordered_map<std::string, ImageMetadata> imageMetadataGroundTruth;
-  imageMetadataGroundTruth["AnimatedCube_BaseColor.png"]         = ImageMetadata{ImageDimensions(256, 256), Dali::SamplingMode::BOX_THEN_NEAREST};
-  imageMetadataGroundTruth["AnimatedCube_MetallicRoughness.png"] = ImageMetadata{ImageDimensions(256, 256), Dali::SamplingMode::NEAREST};
-
-  auto metaData = ctx.metaData.mImageMetadata.begin();
-  for(auto& groundTruth : imageMetadataGroundTruth)
-  {
-    DALI_TEST_EQUAL(groundTruth.first, metaData->first);
-    DALI_TEST_EQUAL(groundTruth.second.mMinSize, metaData->second.mMinSize);
-    DALI_TEST_EQUAL(groundTruth.second.mSamplingMode, metaData->second.mSamplingMode);
-    ++metaData;
-  }
-
-  ShaderDefinitionFactory sdf;
-  sdf.SetResources(ctx.resources);
-
-  InitializeGltfLoader();
-  LoadGltfScene(TEST_RESOURCE_DIR "/AnimatedCube.gltf", sdf, ctx.loadResult);
-
-  DALI_TEST_EQUAL(1u, ctx.scene.GetRoots().size());
-  DALI_TEST_EQUAL(9u, ctx.scene.GetNodeCount());
-
-  // Default envmap is used
-  DALI_TEST_EQUAL(1u, ctx.resources.mEnvironmentMaps.size());
-
-  TestApplication app;
-
-  Customization::Choices choices;
-  for(auto iRoot : ctx.scene.GetRoots())
-  {
-    auto resourceRefs = ctx.resources.CreateRefCounter();
-    ctx.scene.CountResourceRefs(iRoot, choices, resourceRefs);
-    ctx.resources.CountEnvironmentReferences(resourceRefs);
-    ctx.resources.LoadResources(resourceRefs, ctx.pathProvider);
-  }
-
-  auto& materials = ctx.resources.mMaterials;
-  DALI_TEST_EQUAL(2u, materials.size());
-  const MaterialDefinition materialGroundTruth[]{
-    {
-      nullptr,
-      MaterialDefinition::ALBEDO | MaterialDefinition::EMISSIVE | MaterialDefinition::OCCLUSION |
-        MaterialDefinition::NORMAL | MaterialDefinition::SPECULAR | MaterialDefinition::SPECULAR_COLOR |
-        (0x80 << MaterialDefinition::ALPHA_CUTOFF_SHIFT),
-      0,
-      Color::WHITE,
-      1.f,
-      0.f,
-      Vector4(1.000, 0.766, 0.336, 1.0),
-      1.f,
-      1.f,
-      Vector3(0.2, 0.1, 0.0),
-      0.0f,
-      0.5f,
-      Vector3(0, 0, 1),
-      true,
-      false,
-      true,
-      false,
-      true,
-      true,
-      {
-        {
-          MaterialDefinition::ALBEDO,
-          {
-            "AnimatedCube_BaseColor.png",
-            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::BOX_THEN_NEAREST,
-          },
-        },
-        {
-          MaterialDefinition::NORMAL,
-          {
-            "AnimatedCube_BaseColor.png",
-            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::BOX_THEN_NEAREST,
-          },
-        },
-        {
-          MaterialDefinition::OCCLUSION,
-          {
-            "AnimatedCube_BaseColor.png",
-            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::BOX_THEN_NEAREST,
-          },
-        },
-        {
-          MaterialDefinition::EMISSIVE,
-          {
-            "AnimatedCube_BaseColor.png",
-            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::BOX_THEN_NEAREST,
-          },
-        },
-        {
-          MaterialDefinition::SPECULAR,
-          {
-            "AnimatedCube_BaseColor.png",
-            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::BOX_THEN_NEAREST,
-          },
-        },
-        {
-          MaterialDefinition::SPECULAR_COLOR,
-          {
-            "AnimatedCube_BaseColor.png",
-            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::BOX_THEN_NEAREST,
-          },
-        },
-      },
-    },
-    {
-      nullptr,
-      MaterialDefinition::ALBEDO | MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS |
-        MaterialDefinition::EMISSIVE | MaterialDefinition::OCCLUSION | MaterialDefinition::NORMAL |
-        MaterialDefinition::GLTF_CHANNELS,
-      0,
-      Color::WHITE,
-      1.f,
-      0.f,
-      Vector4(1.000, 0.766, 0.336, 1.0),
-      1.f,
-      1.f,
-      Vector3(0.2, 0.1, 0.0),
-      0.04f,
-      1.0f,
-      Vector3::ONE,
-      true,
-      true,
-      true,
-      false,
-      true,
-      false,
-      {
-        {
-          MaterialDefinition::ALBEDO,
-          {
-            "AnimatedCube_BaseColor.png",
-            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::BOX_THEN_NEAREST,
-          },
-        },
-        {
-          MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS | MaterialDefinition::GLTF_CHANNELS,
-          {
-            "AnimatedCube_MetallicRoughness.png",
-            SamplerFlags::Encode(FilterMode::NEAREST_MIPMAP_LINEAR, FilterMode::NEAREST, WrapMode::CLAMP_TO_EDGE, WrapMode::MIRRORED_REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::NEAREST,
-          },
-        },
-        {
-          MaterialDefinition::NORMAL,
-          {
-            "AnimatedCube_BaseColor.png",
-            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::BOX_THEN_NEAREST,
-          },
-        },
-        {
-          MaterialDefinition::OCCLUSION,
-          {
-            "AnimatedCube_BaseColor.png",
-            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::BOX_THEN_NEAREST,
-          },
-        },
-        {
-          MaterialDefinition::EMISSIVE,
-          {
-            "AnimatedCube_BaseColor.png",
-            SamplerFlags::Encode(FilterMode::LINEAR_MIPMAP_LINEAR, FilterMode::LINEAR, WrapMode::CLAMP_TO_EDGE, WrapMode::REPEAT),
-            ImageDimensions(256, 256),
-            SamplingMode::BOX_THEN_NEAREST,
-          },
-        },
-      },
-    },
-  };
-
-  auto iMaterial = materials.begin();
-  auto iMetadata = ctx.metaData.mImageMetadata.begin();
-  for(auto& m : materialGroundTruth)
-  {
-    printf("material %ld\n", iMaterial - materials.begin());
-    auto& md = iMaterial->first;
-    DALI_TEST_EQUAL(md.mFlags, m.mFlags);
-    DALI_TEST_EQUAL(md.mEnvironmentIdx, m.mEnvironmentIdx);
-    DALI_TEST_EQUAL(md.mColor, m.mColor);
-    DALI_TEST_EQUAL(md.mMetallic, m.mMetallic);
-    DALI_TEST_EQUAL(md.mRoughness, m.mRoughness);
-    DALI_TEST_EQUAL(md.mBaseColorFactor, m.mBaseColorFactor);
-    DALI_TEST_EQUAL(md.mNormalScale, m.mNormalScale);
-    DALI_TEST_EQUAL(md.mOcclusionStrength, m.mOcclusionStrength);
-    DALI_TEST_EQUAL(md.mEmissiveFactor, m.mEmissiveFactor);
-    DALI_TEST_EQUAL(md.mDielectricSpecular, m.mDielectricSpecular);
-    DALI_TEST_EQUAL(md.mSpecularFactor, m.mSpecularFactor);
-    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.mNeedNormalTexture, m.mNeedNormalTexture);
-    DALI_TEST_EQUAL(md.mIsOpaque, m.mIsOpaque);
-    DALI_TEST_EQUAL(md.mIsMask, m.mIsMask);
-
-    DALI_TEST_EQUAL(md.mTextureStages.size(), m.mTextureStages.size());
-    auto iTexture = md.mTextureStages.begin();
-    for(auto& ts : m.mTextureStages)
-    {
-      printf("texture %ld\n", iTexture - md.mTextureStages.begin());
-      DALI_TEST_EQUAL(iTexture->mSemantic, ts.mSemantic);
-      DALI_TEST_EQUAL(iTexture->mTexture.mImageUri, ts.mTexture.mImageUri);
-      DALI_TEST_EQUAL(uint32_t(iTexture->mTexture.mSamplerFlags), uint32_t(ts.mTexture.mSamplerFlags)); // don't interpret it as a character
-      DALI_TEST_EQUAL(iTexture->mTexture.mMinImageDimensions, ts.mTexture.mMinImageDimensions);
-      DALI_TEST_EQUAL(iTexture->mTexture.mSamplingMode, ts.mTexture.mSamplingMode);
-
-      ++iTexture;
-    }
-    ++iMaterial;
-    ++iMetadata;
-  }
-
-  auto& meshes = ctx.resources.mMeshes;
-  DALI_TEST_EQUAL(2u, meshes.size());
-
-  using Blob     = MeshDefinition::Blob;
-  using Accessor = MeshDefinition::Accessor;
-  const MeshDefinition meshGroundTruth[]{
-    {
-      nullptr,
-      0,
-      Geometry::TRIANGLES,
-      "AnimatedCube.bin",
-      Accessor{Blob{0, 0}, {}},
-      Accessor{Blob{0, 0}, {}},
-      Accessor{Blob{0, 0}, {}},
-      Accessor{Blob{0, 0}, {}},
-      Accessor{Blob{0, 0}, {}},
-      Accessor{Blob{0, 0}, {}},
-    },
-    {
-      nullptr,
-      0,
-      Geometry::TRIANGLES,
-      "AnimatedCube.bin",
-      Accessor{Blob{0, 0}, {}},
-      Accessor{Blob{0, 0}, {}},
-      Accessor{Blob{0, 0}, {}},
-      Accessor{Blob{0, 0}, {}},
-      Accessor{Blob{0, 0}, {}},
-      Accessor{Blob{0, 0}, {}},
-    },
-  };
-
-  auto iMesh = meshes.begin();
-  for(auto& m : meshGroundTruth)
-  {
-    printf("mesh %ld\n", iMesh - meshes.begin());
-
-    auto& md = iMesh->first;
-    DALI_TEST_EQUAL(md.mFlags, m.mFlags);
-    DALI_TEST_EQUAL(md.mPrimitiveType, m.mPrimitiveType);
-    for(auto mp : {
-          &MeshDefinition::mIndices,
-          &MeshDefinition::mPositions,
-          &MeshDefinition::mNormals,
-          &MeshDefinition::mTexCoords,
-          &MeshDefinition::mColors,
-          &MeshDefinition::mTangents,
-          &MeshDefinition::mJoints0,
-          &MeshDefinition::mWeights0})
-    {
-      DALI_TEST_EQUAL((md.*mp).IsDefined(), (m.*mp).IsDefined());
-      DALI_TEST_EQUAL((md.*mp).mBlob.IsDefined(), (m.*mp).mBlob.IsDefined());
-    }
-
-    DALI_TEST_EQUAL(md.mBlendShapeHeader.IsDefined(), m.mBlendShapeHeader.IsDefined());
-
-    ++iMesh;
-  }
-
-  DALI_TEST_EQUAL(2u, ctx.resources.mShaders.size());
-  DALI_TEST_EQUAL(0u, ctx.resources.mSkeletons.size());
-
-  DALI_TEST_EQUAL(6u, ctx.cameras.size());
-  DALI_TEST_EQUAL(0u, ctx.lights.size());
-  DALI_TEST_EQUAL(1u, ctx.animations.size());
-  DALI_TEST_EQUAL(0u, ctx.animationGroups.size());
-
-  END_TEST;
-}
-
-int UtcDaliGltfLoaderSuccess2(void)
-{
-  Context                 ctx;
-  ShaderDefinitionFactory sdf;
-  sdf.SetResources(ctx.resources);
-
-  InitializeGltfLoader();
-  LoadGltfScene(TEST_RESOURCE_DIR "/AnimatedCubeStride.gltf", sdf, ctx.loadResult);
-
-  DALI_TEST_EQUAL(1u, ctx.scene.GetRoots().size());
-  DALI_TEST_EQUAL(1u, ctx.scene.GetNodeCount());
-
-  TestApplication app;
-
-  Customization::Choices choices;
-  for(auto iRoot : ctx.scene.GetRoots())
-  {
-    auto resourceRefs = ctx.resources.CreateRefCounter();
-    ctx.scene.CountResourceRefs(iRoot, choices, resourceRefs);
-    ctx.resources.LoadResources(resourceRefs, ctx.pathProvider);
-  }
-
-  DALI_TEST_EQUAL(true, ctx.resources.mMeshes[0u].first.mPositions.IsDefined());
-  DALI_TEST_EQUAL(432, ctx.resources.mMeshes[0u].first.mPositions.mBlob.mLength);
-
-  END_TEST;
-}
-
-int UtcDaliGltfLoaderSuccessShort(void)
-{
-  TestApplication app;
-
-  const std::string resourcePath = TEST_RESOURCE_DIR "/";
-  auto              pathProvider = [resourcePath](ResourceType::Value)
-  {
-    return resourcePath;
-  };
-
-  Customization::Choices choices;
-  for(auto modelName : {
-        "2CylinderEngine",
-        "AnimatedMorphCube",
-        "AnimatedMorphSphere",
-        "AnimatedTriangle",
-        "BoxAnimated",
-        "CesiumMan",
-        "CesiumMilkTruck",
-        "EnvironmentTest",
-        "MetalRoughSpheres",
-        "MorphPrimitivesTest",
-        "MRendererTest",
-        "SimpleSparseAccessor",
-        "AnimatedCube",
-      })
-  {
-    Context ctx;
-
-    ShaderDefinitionFactory sdf;
-
-    auto& resources = ctx.resources;
-    resources.mEnvironmentMaps.push_back({});
-
-    sdf.SetResources(resources);
-
-    printf("%s\n", modelName);
-    InitializeGltfLoader();
-    LoadGltfScene(resourcePath + modelName + ".gltf", sdf, ctx.loadResult);
-    DALI_TEST_CHECK(ctx.scene.GetNodeCount() > 0);
-
-    auto& scene = ctx.scene;
-    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])
-        {
-          auto raw = resources.mMeshes[i0].first.LoadRaw(resourcePath, resources.mBuffers);
-          DALI_TEST_CHECK(!raw.mAttribs.empty());
-
-          resources.mMeshes[i0].second = resources.mMeshes[i0].first.Load(std::move(raw));
-          DALI_TEST_CHECK(resources.mMeshes[i0].second.geometry);
-        }
-      }
-    }
-  }
-
-  END_TEST;
-}
-
-int UtcDaliGltfLoaderMRendererTest(void)
-{
-  Context ctx;
-
-  ShaderDefinitionFactory sdf;
-  sdf.SetResources(ctx.resources);
-  auto& resources = ctx.resources;
-
-  InitializeGltfLoader();
-  LoadGltfScene(TEST_RESOURCE_DIR "/MRendererTest.gltf", sdf, ctx.loadResult);
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 1u);
-  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mName, "RootNode");
-  DALI_TEST_EQUAL(scene.GetNode(roots[0])->mScale, Vector3(1.0f, 1.0f, 1.0f));
-
-  DALI_TEST_EQUAL(scene.GetNodeCount(), 1u);
-
-  ViewProjection viewProjection;
-  Transforms     xforms{
-    MatrixStack{},
-    viewProjection};
-  NodeDefinition::CreateParams nodeParams{
-    resources,
-    xforms,
-  };
-
-  Customization::Choices choices;
-
-  TestApplication app;
-
-  Actor root = Actor::New();
-  SetActorCentered(root);
-  for(auto iRoot : roots)
-  {
-    auto resourceRefs = resources.CreateRefCounter();
-    scene.CountResourceRefs(iRoot, choices, resourceRefs);
-    resources.CountEnvironmentReferences(resourceRefs);
-    resources.LoadResources(resourceRefs, ctx.pathProvider);
-    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
-    {
-      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
-      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
-      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
-      root.Add(actor);
-    }
-  }
-
-  DALI_TEST_EQUAL(root.GetChildCount(), 1u);
-  Actor child = root.GetChildAt(0);
-
-  DALI_TEST_EQUAL(child.GetProperty(Actor::Property::NAME).Get<std::string>(), "RootNode");
-  DALI_TEST_EQUAL(child.GetProperty(Actor::Property::SCALE).Get<Vector3>(), Vector3(1.0f, 1.0f, 1.0f));
-  DALI_TEST_EQUAL(child.GetRendererCount(), 1u);
-  DALI_TEST_EQUAL(child.GetRendererAt(0).GetTextures().GetTextureCount(), 4u);
-
-  DALI_TEST_EQUAL(child.GetRendererCount(), 1u);
-  DALI_TEST_EQUAL(child.GetRendererAt(0u).GetProperty<decltype(BlendMode::ON)>(Renderer::Property::BLEND_MODE), BlendMode::ON);
-
-  END_TEST;
-}
-
-int UtcDaliGltfLoaderAnimationLoadingTest(void)
-{
-  Context ctx;
-
-  ShaderDefinitionFactory sdf;
-  sdf.SetResources(ctx.resources);
-  auto& resources = ctx.resources;
-
-  InitializeGltfLoader();
-  LoadGltfScene(TEST_RESOURCE_DIR "/CesiumMan_e.gltf", sdf, ctx.loadResult);
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 1u);
-
-  ViewProjection viewProjection;
-  Transforms     xforms{
-    MatrixStack{},
-    viewProjection};
-  NodeDefinition::CreateParams nodeParams{
-    resources,
-    xforms,
-  };
-
-  Customization::Choices choices;
-
-  TestApplication app;
-
-  Actor root = Actor::New();
-  SetActorCentered(root);
-  for(auto iRoot : roots)
-  {
-    auto resourceRefs = resources.CreateRefCounter();
-    scene.CountResourceRefs(iRoot, choices, resourceRefs);
-    resources.CountEnvironmentReferences(resourceRefs);
-    resources.LoadResources(resourceRefs, ctx.pathProvider);
-    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
-    {
-      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
-      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
-      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
-      root.Add(actor);
-    }
-  }
-
-  DALI_TEST_EQUAL(ctx.loadResult.mAnimationDefinitions.size(), 1u);
-  DALI_TEST_EQUAL(ctx.loadResult.mAnimationDefinitions[0].mProperties.size(), 57u);
-
-  uint32_t id = ctx.loadResult.mScene.GetNode(ctx.loadResult.mAnimationDefinitions[0].mProperties[0].mNodeIndex)->mNodeId;
-  DALI_TEST_EQUAL(id, root.FindChildByName("Skeleton_torso_joint_1").GetProperty<int32_t>(Dali::Actor::Property::ID));
-
-  END_TEST;
-}
-
-int UtcDaliGltfLoaderImageFromBufferView(void)
-{
-  Context ctx;
-
-  ShaderDefinitionFactory sdf;
-  sdf.SetResources(ctx.resources);
-  auto& resources = ctx.resources;
-
-  InitializeGltfLoader();
-  LoadGltfScene(TEST_RESOURCE_DIR "/EnvironmentTest_b.gltf", sdf, ctx.loadResult);
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 1u);
-
-  ViewProjection viewProjection;
-  Transforms     xforms{
-    MatrixStack{},
-    viewProjection};
-  NodeDefinition::CreateParams nodeParams{
-    resources,
-    xforms,
-  };
-
-  Customization::Choices choices;
-
-  TestApplication app;
-
-  Actor root = Actor::New();
-  SetActorCentered(root);
-  for(auto iRoot : roots)
-  {
-    auto resourceRefs = resources.CreateRefCounter();
-    scene.CountResourceRefs(iRoot, choices, resourceRefs);
-    resources.CountEnvironmentReferences(resourceRefs);
-    resources.LoadResources(resourceRefs, ctx.pathProvider);
-    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
-    {
-      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
-      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
-      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
-      root.Add(actor);
-    }
-  }
-
-  DALI_TEST_CHECK(resources.mMaterials[0].second.GetTextureCount() > 1);
-  DALI_TEST_EQUAL(resources.mMaterials[0].second.GetTexture(0).GetWidth(), 256);
-  DALI_TEST_EQUAL(resources.mMaterials[0].second.GetTexture(0).GetHeight(), 256);
-
-  END_TEST;
-}
-
-int UtcDaliGltfLoaderUint8Indices(void)
-{
-  Context ctx;
-
-  ShaderDefinitionFactory sdf;
-  sdf.SetResources(ctx.resources);
-  auto& resources = ctx.resources;
-
-  InitializeGltfLoader();
-  LoadGltfScene(TEST_RESOURCE_DIR "/AlphaBlendModeTest.gltf", sdf, ctx.loadResult);
-
-  auto& scene = ctx.scene;
-  auto& roots = scene.GetRoots();
-  DALI_TEST_EQUAL(roots.size(), 1u);
-
-  ViewProjection viewProjection;
-  Transforms     xforms{
-    MatrixStack{},
-    viewProjection};
-  NodeDefinition::CreateParams nodeParams{
-    resources,
-    xforms,
-  };
-
-  Customization::Choices choices;
-
-  TestApplication app;
-
-  Actor root = Actor::New();
-  SetActorCentered(root);
-  for(auto iRoot : roots)
-  {
-    auto resourceRefs = resources.CreateRefCounter();
-    scene.CountResourceRefs(iRoot, choices, resourceRefs);
-    resources.CountEnvironmentReferences(resourceRefs);
-    resources.LoadResources(resourceRefs, ctx.pathProvider);
-    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
-    {
-      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
-      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
-      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
-      root.Add(actor);
-    }
-  }
-
-  DALI_TEST_CHECK(root.FindChildByName("Bed"));
-  DALI_TEST_CHECK(root.FindChildByName("DecalBlend"));
-  DALI_TEST_CHECK(root.FindChildByName("DecalOpaque"));
-
-  END_TEST;
-}
index 20c1bcbc62aa84c93085f98a90437bcb0a4e17a6..162e48e947682bd32a61688c5437b97e3908e1a0 100644 (file)
 // Enable debug log for test coverage
 #define DEBUG_ENABLED 1
 
-#include "dali-scene3d/public-api/loader/resource-bundle.h"
-#include "dali-scene3d/public-api/loader/utils.h"
 #include <dali-test-suite-utils.h>
 #include <string_view>
+#include "dali-scene3d/public-api/loader/resource-bundle.h"
+#include "dali-scene3d/public-api/loader/utils.h"
 
 using namespace Dali;
 using namespace Dali::Scene3D::Loader;
@@ -34,9 +34,9 @@ int UtcDaliResourceRefCounts(void)
   resourceBundle.mMeshes.resize(17);
   resourceBundle.mMaterials.resize(19);
 
-  int i = 0;
+  int              i = 0;
   std::vector<int> testEnvironmentReferences(resourceBundle.mEnvironmentMaps.size());
-  for (auto& m : resourceBundle.mMaterials)
+  for(auto& m : resourceBundle.mMaterials)
   {
     Index iEnv = 0;
     iEnv += (i % 3) == 0;
@@ -56,9 +56,12 @@ int UtcDaliResourceRefCounts(void)
   DALI_TEST_EQUAL(counter[ResourceType::Material].Size(), resourceBundle.mMaterials.size());
 
   std::fill(counter[ResourceType::Material].begin(), counter[ResourceType::Material].end(), 1u);
-  resourceBundle.CountEnvironmentReferences(counter);
-  i = 0;
-  for (auto& er: counter[ResourceType::Environment])
+  resourceBundle.mReferenceCounts = std::move(counter);
+  resourceBundle.CountEnvironmentReferences();
+
+  const ResourceRefCounts& referenceCounts = resourceBundle.mReferenceCounts;
+  i                                        = 0;
+  for(auto& er : referenceCounts[ResourceType::Environment])
   {
     DALI_TEST_EQUAL(er, testEnvironmentReferences[i]);
     ++i;
index 12f96054952a5b63ac2cdcf94bbf157d4a60817a..0a269a52053ab88bb9f097166a873b8d263dcb4d 100644 (file)
@@ -24,7 +24,6 @@
 #include <dali-test-suite-utils.h>
 #include <set>
 #include <string_view>
-#include "dali-scene3d/public-api/loader/gltf2-loader.h"
 #include "dali-scene3d/public-api/loader/node-definition.h"
 #include "dali-scene3d/public-api/loader/resource-bundle.h"
 #include "dali-scene3d/public-api/loader/shader-definition-factory.h"
index 01d2eba2a1148df071446f34cea48b99415d02fb..0a26bcc0f33516afb8f3a78d341dcf9977e522c1 100644 (file)
@@ -57,12 +57,6 @@ public:
     return cache.loadSceneConditionalWait;
   }
 
-  Dali::ConditionalWait& GetLoadRawResourceConditionalWaitInstance(std::string modelUri)
-  {
-    ModelCache& cache = mModelCache[modelUri];
-    return cache.loadRawResourceConditionalWait;
-  }
-
   void ReferenceModelCache(std::string modelUri)
   {
     ModelCache& cache = mModelCache[modelUri];
@@ -129,7 +123,6 @@ private:
 
     uint32_t              refCount{0};                      ///< The reference count of this model cache.
     Dali::ConditionalWait loadSceneConditionalWait{};       ///< The conditionalWait instance used to synchronise the loading of the scene for the same model in different threads.
-    Dali::ConditionalWait loadRawResourceConditionalWait{}; ///< The conditionalWait instance used to synchronise the loading of the shared raw resources for the same model in different threads.
 
     bool isSceneLoaded{false};  ///< Whether the scene of the model has been loaded.
     bool isSceneLoading{false}; ///< Whether the scene loading of the model is in progress.
@@ -192,12 +185,6 @@ Dali::ConditionalWait& ModelCacheManager::GetLoadSceneConditionalWaitInstance(st
   return impl.GetLoadSceneConditionalWaitInstance(modelUri);
 }
 
-Dali::ConditionalWait& ModelCacheManager::GetLoadRawResourceConditionalWaitInstance(std::string modelUri)
-{
-  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
-  return impl.GetLoadRawResourceConditionalWaitInstance(modelUri);
-}
-
 void ModelCacheManager::ReferenceModelCache(std::string modelUri)
 {
   ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
index a4279450392afdfd13b8541bb5a49a27ff23465f..0d30b269f29e4af77c115381b0b78a97277aa7c6 100644 (file)
@@ -85,14 +85,6 @@ public:
    */
   Dali::ConditionalWait& GetLoadSceneConditionalWaitInstance(std::string modelUri);
 
-  /**
-   * @brief Retrieves the ConditionalWait object to synchronize the raw resources loading of the
-   * model with the given URI between multiple threads.
-   * @param[in] modelUri The unique model URI with its absolute path.
-   * @return The ConditionalWait object.
-   */
-  Dali::ConditionalWait& GetLoadRawResourceConditionalWaitInstance(std::string modelUri);
-
   /**
    * @brief Reference the cache of the model with the given URI.
    * This will increment the reference count of the load result by 1.
index aaec4299c5245aa068cf2b7e92d0c202495b6b9a..b979c226a265bfb84780ec41b35d8a9d2b02fcad 100644 (file)
 #include <dali/integration-api/debug.h>
 #include <filesystem>
 
-// INTERNAL INCLUDES
-#include <dali-scene3d/internal/common/model-cache-manager.h>
-#include <dali-scene3d/public-api/loader/animation-definition.h>
-#include <dali-scene3d/public-api/loader/camera-parameters.h>
-#include <dali-scene3d/public-api/loader/dli-loader.h>
-#include <dali-scene3d/public-api/loader/gltf2-loader.h>
-#include <dali-scene3d/public-api/loader/light-parameters.h>
-#include <dali-scene3d/public-api/loader/node-definition.h>
-#include <dali-scene3d/public-api/loader/shader-definition-factory.h>
-
 namespace Dali
 {
 namespace Scene3D
@@ -41,20 +31,15 @@ namespace Internal
 namespace
 {
 static constexpr Vector3 Y_DIRECTION(1.0f, -1.0f, 1.0f);
-
-static constexpr std::string_view OBJ_EXTENSION      = ".obj";
-static constexpr std::string_view GLTF_EXTENSION     = ".gltf";
-static constexpr std::string_view DLI_EXTENSION      = ".dli";
-static constexpr std::string_view METADATA_EXTENSION = "metadata";
 } // namespace
 
 ModelLoadTask::ModelLoadTask(const std::string& modelUrl, const std::string& resourceDirectoryUrl, CallbackBase* callback)
 : AsyncTask(callback),
   mModelUrl(modelUrl),
   mResourceDirectoryUrl(resourceDirectoryUrl),
-  mHasSucceeded(false),
-  mModelCacheManager(ModelCacheManager::Get()),
-  mLoadResult(mModelCacheManager.GetModelLoadResult(modelUrl))
+  mModelCacheManager(Scene3D::Internal::ModelCacheManager::Get()),
+  mLoadResult(mModelCacheManager.GetModelLoadResult(mModelUrl)),
+  mHasSucceeded(false)
 {
 }
 
@@ -64,124 +49,51 @@ ModelLoadTask::~ModelLoadTask()
 
 void ModelLoadTask::Process()
 {
-  uint32_t               cacheRefCount                  = mModelCacheManager.GetModelCacheRefCount(mModelUrl);
-  Dali::ConditionalWait& loadSceneConditionalWait       = mModelCacheManager.GetLoadSceneConditionalWaitInstance(mModelUrl);
-  Dali::ConditionalWait& loadRawResourceConditionalWait = mModelCacheManager.GetLoadRawResourceConditionalWaitInstance(mModelUrl);
-
-  std::filesystem::path modelUrl(mModelUrl);
   if(mResourceDirectoryUrl.empty())
   {
+    std::filesystem::path modelUrl(mModelUrl);
     mResourceDirectoryUrl = std::string(modelUrl.parent_path()) + "/";
   }
-  std::string extension = modelUrl.extension();
-  std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
 
-  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);
+
+  bool                   loadSucceeded            = false;
+  Dali::ConditionalWait& loadSceneConditionalWait = mModelCacheManager.GetLoadSceneConditionalWaitInstance(mModelUrl);
   {
     ConditionalWait::ScopedLock lock(loadSceneConditionalWait);
-
-    while(cacheRefCount > 1 && mModelCacheManager.IsSceneLoading(mModelUrl))
+    if(mModelCacheManager.IsSceneLoaded(mModelUrl))
     {
-      loadSceneConditionalWait.Wait();
+      loadSucceeded = true;
     }
-  }
-
-  {
-    ConditionalWait::ScopedLock lock(loadSceneConditionalWait);
-
-    if(!mModelCacheManager.IsSceneLoaded(mModelUrl))
+    else
     {
       mModelCacheManager.SetSceneLoading(mModelUrl, true);
 
-      std::filesystem::path metaDataUrl = modelUrl;
-      metaDataUrl.replace_extension(METADATA_EXTENSION.data());
-
-      Dali::Scene3D::Loader::LoadSceneMetadata(metaDataUrl.c_str(), mLoadResult.mSceneMetadata);
-
-      mLoadResult.mAnimationDefinitions.clear();
+      loadSucceeded = mModelLoader->LoadModel(pathProvider, true);
 
-      if(extension == DLI_EXTENSION)
+      // Mesh of glTF and dli is defined in right hand coordinate system, with positive Y for Up direction.
+      // Because DALi uses left hand system, Y direciton will be flipped for environment map sampling.
+      for(auto&& env : GetResources().mEnvironmentMaps)
       {
-        Dali::Scene3D::Loader::DliLoader              loader;
-        Dali::Scene3D::Loader::DliLoader::InputParams input{
-          pathProvider(Dali::Scene3D::Loader::ResourceType::Mesh),
-          nullptr,
-          {},
-          {},
-          nullptr,
-          {}};
-        Dali::Scene3D::Loader::DliLoader::LoadParams loadParams{input, mLoadResult};
-        if(!loader.LoadScene(mModelUrl, loadParams))
-        {
-          DALI_LOG_ERROR("Failed to load scene from '%s': %s\n", mModelUrl.c_str(), loader.GetParseError().c_str());
-
-          mModelCacheManager.SetSceneLoaded(mModelUrl, false);
-          mModelCacheManager.SetSceneLoading(mModelUrl, false);
-          mModelCacheManager.UnreferenceModelCache(mModelUrl);
-
-          return;
-        }
+        env.first.mYDirection = Y_DIRECTION;
       }
-      else if(extension == GLTF_EXTENSION)
-      {
-        Dali::Scene3D::Loader::ShaderDefinitionFactory sdf;
-        sdf.SetResources(mLoadResult.mResources);
-        Dali::Scene3D::Loader::LoadGltfScene(mModelUrl, sdf, mLoadResult);
-      }
-      else
-      {
-        DALI_LOG_ERROR("Unsupported model type.\n");
-
-        mModelCacheManager.SetSceneLoaded(mModelUrl, false);
-        mModelCacheManager.SetSceneLoading(mModelUrl, false);
-        mModelCacheManager.UnreferenceModelCache(mModelUrl);
 
-        return;
-      }
-
-      mModelCacheManager.SetSceneLoaded(mModelUrl, true);
       mModelCacheManager.SetSceneLoading(mModelUrl, false);
+      mModelCacheManager.SetSceneLoaded(mModelUrl, loadSucceeded);
     }
   }
 
-  loadSceneConditionalWait.Notify();
-
+  if(!loadSucceeded)
   {
-    ConditionalWait::ScopedLock lock(loadRawResourceConditionalWait);
-
-    while(cacheRefCount > 1 && mLoadResult.mResources.mRawResourcesLoading)
-    {
-      loadRawResourceConditionalWait.Wait();
-    }
+    DALI_LOG_ERROR("Failed to load scene from '%s'\n", mModelUrl.c_str());
+    return;
   }
 
-  {
-    ConditionalWait::ScopedLock lock(loadRawResourceConditionalWait);
-
-    mResourceRefCount = std::move(mLoadResult.mResources.CreateRefCounter());
-
-    for(auto iRoot : mLoadResult.mScene.GetRoots())
-    {
-      mLoadResult.mScene.CountResourceRefs(iRoot, mResourceChoices, mResourceRefCount);
-    }
-
-    mLoadResult.mResources.CountEnvironmentReferences(mResourceRefCount);
-
-    mLoadResult.mResources.LoadRawResources(mResourceRefCount, pathProvider);
-
-    // glTF Mesh is defined in right hand coordinate system, with positive Y for Up direction.
-    // Because DALi uses left hand system, Y direciton will be flipped for environment map sampling.
-    for(auto&& env : mLoadResult.mResources.mEnvironmentMaps)
-    {
-      env.first.mYDirection = Y_DIRECTION;
-    }
-  }
-
-  loadRawResourceConditionalWait.Notify();
-
   mHasSucceeded = true;
 }
 
@@ -195,6 +107,31 @@ bool ModelLoadTask::HasSucceeded() const
   return mHasSucceeded;
 }
 
+Dali::Scene3D::Loader::SceneDefinition& ModelLoadTask::GetScene() const
+{
+  return mModelLoader->GetScene();
+}
+
+Dali::Scene3D::Loader::ResourceBundle& ModelLoadTask::GetResources() const
+{
+  return mModelLoader->GetResources();
+}
+
+std::vector<Dali::Scene3D::Loader::AnimationDefinition>& ModelLoadTask::GetAnimations() const
+{
+  return mModelLoader->GetAnimations();
+}
+
+std::vector<Dali::Scene3D::Loader::CameraParameters>& ModelLoadTask::GetCameras() const
+{
+  return mModelLoader->GetCameras();
+}
+
+Dali::Scene3D::Loader::Customization::Choices& ModelLoadTask::GetResourceChoices()
+{
+  return mModelLoader->GetResourceChoices();
+}
+
 } // namespace Internal
 
 } // namespace Scene3D
index bfafeb00da7f691bbc12087573b536f50993f625..a880748202293535d84b6bbc93d307e43eded522 100644 (file)
@@ -18,6 +18,7 @@
  */
 
 // EXTERNAL INCLUDES
+#include <dali/public-api/adaptor-framework/async-task-manager.h>
 #include <dali/public-api/common/intrusive-ptr.h>
 #include <dali/public-api/common/vector-wrapper.h>
 #include <dali/public-api/images/pixel-data.h>
@@ -26,9 +27,9 @@
 // INTERNAL INCLUDES
 #include <dali-scene3d/internal/common/model-cache-manager.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/scene-definition.h>
 #include <dali/devel-api/threading/conditional-wait.h>
-#include <dali/public-api/adaptor-framework/async-task-manager.h>
 
 namespace Dali
 {
@@ -72,6 +73,36 @@ public:
    */
   bool HasSucceeded() const;
 
+  /**
+   * @brief Retrieves loaded scene
+   * @return SceneDefinition that is loaded from file
+   */
+  Dali::Scene3D::Loader::SceneDefinition& GetScene() const;
+
+  /**
+   * @brief Retrieves resource bunder that includes resource information
+   * @return ResourceBundle for model resources
+   */
+  Dali::Scene3D::Loader::ResourceBundle& GetResources() const;
+
+  /**
+   * @brief Retrieves loaded AnimationDefinition
+   * @return AnimationDefinition that is loaded from file
+   */
+  std::vector<Dali::Scene3D::Loader::AnimationDefinition>& GetAnimations() const;
+
+  /**
+   * @brief Retrieves loaded CameraParameters
+   * @return CameraParameters list that is loaded from file
+   */
+  std::vector<Dali::Scene3D::Loader::CameraParameters>& GetCameras() const;
+
+  /**
+   * @brief Retrieves ResourceChoices
+   * @return Choices for loaded Resources
+   */
+  Dali::Scene3D::Loader::Customization::Choices& GetResourceChoices();
+
 private:
   // Undefined
   ModelLoadTask(const ModelLoadTask& task) = delete;
@@ -79,16 +110,12 @@ private:
   // Undefined
   ModelLoadTask& operator=(const ModelLoadTask& task) = delete;
 
-public:
-  std::string mModelUrl;
-  std::string mResourceDirectoryUrl;
-
-  Dali::Scene3D::Loader::Customization::Choices mResourceChoices;
-  Dali::Scene3D::Loader::ResourceRefCounts      mResourceRefCount;
-  bool                                          mHasSucceeded;
-
-  ModelCacheManager                 mModelCacheManager;
-  Dali::Scene3D::Loader::LoadResult mLoadResult;
+  std::string                                         mModelUrl;
+  std::string                                         mResourceDirectoryUrl;
+  std::shared_ptr<Dali::Scene3D::Loader::ModelLoader> mModelLoader;
+  ModelCacheManager                                   mModelCacheManager;
+  Dali::Scene3D::Loader::LoadResult                   mLoadResult;
+  bool                                                mHasSucceeded;
 };
 
 } // namespace Internal
index b7cb6ef86bfeb2f8358ad29278eb3db4a18d44c6..5aa4a5fd8c37bf3af231c7eb9f779210b4891c05 100644 (file)
@@ -36,8 +36,6 @@
 #include <dali-scene3d/public-api/controls/model/model.h>
 #include <dali-scene3d/public-api/loader/animation-definition.h>
 #include <dali-scene3d/public-api/loader/camera-parameters.h>
-#include <dali-scene3d/public-api/loader/dli-loader.h>
-#include <dali-scene3d/public-api/loader/gltf2-loader.h>
 #include <dali-scene3d/public-api/loader/light-parameters.h>
 #include <dali-scene3d/public-api/loader/load-result.h>
 #include <dali-scene3d/public-api/loader/node-definition.h>
@@ -117,7 +115,8 @@ void ConfigureBlendShapeShaders(
   Dali::Scene3D::Loader::ResourceBundle& resources, const Dali::Scene3D::Loader::SceneDefinition& scene, Actor root, std::vector<Dali::Scene3D::Loader::BlendshapeShaderConfigurationRequest>&& requests)
 {
   std::vector<std::string> errors;
-  auto                     onError = [&errors](const std::string& msg) { errors.push_back(msg); };
+  auto                     onError = [&errors](const std::string& msg)
+  { errors.push_back(msg); };
   if(!scene.ConfigureBlendshapeShaders(resources, root, std::move(requests), onError))
   {
     Dali::Scene3D::Loader::ExceptionFlinger flinger(ASSERT_LOCATION);
@@ -447,8 +446,6 @@ void Model::OnSceneConnection(int depth)
     {
       ModelCacheManager::Get().ReferenceModelCache(mModelUrl);
     }
-
-    Scene3D::Loader::InitializeGltfLoader();
     mModelLoadTask = new ModelLoadTask(mModelUrl, mResourceDirectoryUrl, MakeCallback(this, &Model::OnModelLoadComplete));
     Dali::AsyncTaskManager::Get().AddTask(mModelLoadTask);
   }
@@ -732,13 +729,14 @@ void Model::OnModelLoadComplete()
   mRenderableActors.clear();
   CollectRenderableActor(mModelRoot);
 
-  CreateAnimations(mModelLoadTask->mLoadResult.mScene);
+  auto& resources = mModelLoadTask->GetResources();
+  auto& scene     = mModelLoadTask->GetScene();
+  CreateAnimations(scene);
   ResetCameraParameters();
-
-  if(!mModelLoadTask->mLoadResult.mResources.mEnvironmentMaps.empty())
+  if(!resources.mEnvironmentMaps.empty())
   {
-    mDefaultDiffuseTexture  = mModelLoadTask->mLoadResult.mResources.mEnvironmentMaps.front().second.mDiffuse;
-    mDefaultSpecularTexture = mModelLoadTask->mLoadResult.mResources.mEnvironmentMaps.front().second.mSpecular;
+    mDefaultDiffuseTexture  = resources.mEnvironmentMaps.front().second.mDiffuse;
+    mDefaultSpecularTexture = resources.mEnvironmentMaps.front().second.mSpecular;
   }
 
   UpdateImageBasedLightTexture();
@@ -820,26 +818,28 @@ void Model::CreateModel()
   mModelRoot.SetProperty(Actor::Property::COLOR_MODE, ColorMode::USE_OWN_MULTIPLY_PARENT_COLOR);
 
   BoundingVolume                                      AABB;
+  auto&                                               resources        = mModelLoadTask->GetResources();
+  auto&                                               scene            = mModelLoadTask->GetScene();
+  auto&                                               resourceChoices  = mModelLoadTask->GetResourceChoices();
   Dali::Scene3D::Loader::Transforms                   xforms{Dali::Scene3D::Loader::MatrixStack{}, Dali::Scene3D::Loader::ViewProjection{}};
-  Dali::Scene3D::Loader::NodeDefinition::CreateParams nodeParams{mModelLoadTask->mLoadResult.mResources, xforms, {}, {}, {}};
+  Dali::Scene3D::Loader::NodeDefinition::CreateParams nodeParams{resources, xforms, {}, {}, {}};
 
   // Generate Dali handles from resource bundle. Note that we generate all scene's resouce immediatly.
-  mModelLoadTask->mLoadResult.mResources.GenerateResources(mModelLoadTask->mResourceRefCount);
-
-  for(auto iRoot : mModelLoadTask->mLoadResult.mScene.GetRoots())
+  resources.GenerateResources();
+  for(auto iRoot : scene.GetRoots())
   {
-    if(auto actor = mModelLoadTask->mLoadResult.mScene.CreateNodes(iRoot, mModelLoadTask->mResourceChoices, nodeParams))
+    if(auto actor = scene.CreateNodes(iRoot, resourceChoices, nodeParams))
     {
-      mModelLoadTask->mLoadResult.mScene.ConfigureSkeletonJoints(iRoot, mModelLoadTask->mLoadResult.mResources.mSkeletons, actor);
-      mModelLoadTask->mLoadResult.mScene.ConfigureSkinningShaders(mModelLoadTask->mLoadResult.mResources, actor, std::move(nodeParams.mSkinnables));
-      ConfigureBlendShapeShaders(mModelLoadTask->mLoadResult.mResources, mModelLoadTask->mLoadResult.mScene, actor, std::move(nodeParams.mBlendshapeRequests));
+      scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
+      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
+      ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
 
-      mModelLoadTask->mLoadResult.mScene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
 
       mModelRoot.Add(actor);
     }
 
-    AddModelTreeToAABB(AABB, mModelLoadTask->mLoadResult.mScene, mModelLoadTask->mResourceChoices, iRoot, nodeParams, Matrix::IDENTITY);
+    AddModelTreeToAABB(AABB, scene, resourceChoices, iRoot, nodeParams, Matrix::IDENTITY);
   }
 
   mNaturalSize = AABB.CalculateSize();
@@ -857,9 +857,10 @@ void Model::CreateModel()
 void Model::CreateAnimations(Dali::Scene3D::Loader::SceneDefinition& scene)
 {
   mAnimations.clear();
-  if(!mModelLoadTask->mLoadResult.mAnimationDefinitions.empty())
+  if(!mModelLoadTask->GetAnimations().empty())
   {
-    auto getActor = [&](const Scene3D::Loader::AnimatedProperty& property) {
+    auto getActor = [&](const Scene3D::Loader::AnimatedProperty& property)
+    {
       if(property.mNodeIndex == Scene3D::Loader::INVALID_INDEX)
       {
         return mModelRoot.FindChildByName(property.mNodeName);
@@ -872,7 +873,7 @@ void Model::CreateAnimations(Dali::Scene3D::Loader::SceneDefinition& scene)
       return mModelRoot.FindChildById(node->mNodeId);
     };
 
-    for(auto&& animation : mModelLoadTask->mLoadResult.mAnimationDefinitions)
+    for(auto&& animation : mModelLoadTask->GetAnimations())
     {
       Dali::Animation anim = animation.ReAnimate(getActor);
       mAnimations.push_back({animation.mName, anim});
@@ -883,10 +884,10 @@ void Model::CreateAnimations(Dali::Scene3D::Loader::SceneDefinition& scene)
 void Model::ResetCameraParameters()
 {
   mCameraParameters.clear();
-  if(!mModelLoadTask->mLoadResult.mCameraParameters.empty())
+  if(!mModelLoadTask->GetCameras().empty())
   {
     // Copy camera parameters.
-    std::copy(mModelLoadTask->mLoadResult.mCameraParameters.begin(), mModelLoadTask->mLoadResult.mCameraParameters.end(), std::back_inserter(mCameraParameters));
+    std::copy(mModelLoadTask->GetCameras().begin(), mModelLoadTask->GetCameras().end(), std::back_inserter(mCameraParameters));
   }
 }
 
index cd1daf542e827ce9109a47a5304120114d418233..939de84e407bc5dc503fb2638cce49352d92d72a 100644 (file)
@@ -262,11 +262,6 @@ private:
    */
   void ResetResourceTask(IntrusivePtr<AsyncTask> asyncTask);
 
-  /**
-   * @brief Request to load a Ibl texture asynchronously
-   */
-  void RequestLoadIblTexture(EnvironmentMapLoadTaskPtr asyncLoadTask, const std::string& url);
-
   /**
    * @brief Notify Resource Ready signal.
    */
index fc80db44c832549760f0d6914d86a4fde02b415c..6503cd8bfb98c0e848ac20a604a7ef753b060d60 100644 (file)
@@ -6,7 +6,9 @@ set(scene3d_src_files ${scene3d_src_files}
        ${scene3d_internal_dir}/common/model-load-task.cpp
        ${scene3d_internal_dir}/controls/model/model-impl.cpp
        ${scene3d_internal_dir}/controls/scene-view/scene-view-impl.cpp
+       ${scene3d_internal_dir}/loader/dli-loader-impl.cpp
        ${scene3d_internal_dir}/loader/gltf2-asset.cpp
+       ${scene3d_internal_dir}/loader/gltf2-loader-impl.cpp
        ${scene3d_internal_dir}/loader/hash.cpp
        ${scene3d_internal_dir}/loader/json-reader.cpp
        ${scene3d_internal_dir}/loader/json-util.cpp
diff --git a/dali-scene3d/internal/loader/dli-loader-impl.cpp b/dali-scene3d/internal/loader/dli-loader-impl.cpp
new file mode 100644 (file)
index 0000000..3a12582
--- /dev/null
@@ -0,0 +1,1851 @@
+/*
+ * 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.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-scene3d/internal/loader/dli-loader-impl.h>
+
+// EXTERNAL INCLUDES
+#include <algorithm>
+#include <cmath>
+#include <filesystem>
+#include <fstream>
+#include <limits>
+#include <memory>
+#include "dali-toolkit/devel-api/builder/json-parser.h"
+#include "dali/devel-api/common/map-wrapper.h"
+#include "dali/integration-api/debug.h"
+#include "dali/public-api/object/property-array.h"
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/internal/loader/json-util.h>
+#include <dali-scene3d/public-api/loader/alpha-function-helper.h>
+#include <dali-scene3d/public-api/loader/animation-definition.h>
+#include <dali-scene3d/public-api/loader/blend-shape-details.h>
+#include <dali-scene3d/public-api/loader/camera-parameters.h>
+#include <dali-scene3d/public-api/loader/ktx-loader.h>
+#include <dali-scene3d/public-api/loader/light-parameters.h>
+#include <dali-scene3d/public-api/loader/load-result.h>
+#include <dali-scene3d/public-api/loader/parse-renderer-state.h>
+#include <dali-scene3d/public-api/loader/scene-definition.h>
+#include <dali-scene3d/public-api/loader/skinning-details.h>
+#include <dali-scene3d/public-api/loader/utils.h>
+
+#define DLI_0_1_COMPATIBILITY
+
+namespace Dali
+{
+using namespace Toolkit;
+
+namespace Scene3D
+{
+namespace Loader
+{
+namespace Internal
+{
+namespace rs = RendererState;
+
+namespace
+{
+const std::string NODES         = "nodes";
+const std::string SCENES        = "scenes";
+const std::string NODE          = "node";
+const std::string URI           = "uri";
+const std::string URL           = "url";
+const std::string CUSTOMIZATION = "customization";
+const std::string HINTS         = "hints";
+const std::string NAME("name");
+const std::string BLEND_SHAPE_HEADER("blendShapeHeader");
+const std::string BLEND_SHAPES("blendShapes");
+const std::string BLEND_SHAPE_VERSION_1_0("1.0");
+const std::string BLEND_SHAPE_VERSION_2_0("2.0");
+const std::string VERSION("version");
+
+const char* const SHADOW_MAP_SIZE   = "shadowMapSize";
+const char* const ORTHOGRAPHIC_SIZE = "orthographicSize";
+const char* const PIXEL_UNITS       = "px";
+
+const char SLASH = '/';
+
+void ReadModelTransform(const TreeNode* node, Quaternion& orientation, Vector3& translation, Vector3& scale)
+{
+  float num[16u] = {.0f};
+
+  if(ReadVector(node->GetChild("matrix"), num, 16u))
+  {
+    Matrix mat(num);
+    mat.GetTransformComponents(translation, orientation, scale);
+  }
+  else
+  {
+    if(ReadVector(node->GetChild("angle"), num, 3u))
+    {
+      orientation = Quaternion(Radian(Degree(num[0u])), Radian(Degree(num[1u])), Radian(Degree(num[2u])));
+    }
+
+    if(ReadVector(node->GetChild("position"), num, 3u))
+    {
+      translation = Vector3(num);
+    }
+  }
+}
+
+bool ReadAttribBlob(const TreeNode* node, MeshDefinition::Blob& buffer)
+{
+  return ReadBlob(node, buffer.mOffset, buffer.mLength);
+}
+
+bool ReadAttribAccessor(const TreeNode* node, MeshDefinition::Accessor& accessor)
+{
+  return ReadBlob(node, accessor.mBlob.mOffset, accessor.mBlob.mLength);
+}
+
+bool ReadColorCode(const TreeNode* node, Vector4& color, DliInputParameter::ConvertColorCode convertColorCode)
+{
+  if(!node || !convertColorCode)
+  {
+    return false;
+  }
+
+  color = convertColorCode(node->GetString());
+
+  return true;
+}
+
+bool ReadColorCodeOrColor(const TreeNode* node, Vector4& color, DliInputParameter::ConvertColorCode convertColorCode)
+{
+  return ReadColorCode(node->GetChild("colorCode"), color, convertColorCode) ||
+         ReadColor(node->GetChild("color"), color);
+}
+
+RendererState::Type ReadRendererState(const TreeNode& tnRendererState)
+{
+  if(tnRendererState.GetType() == TreeNode::INTEGER)
+  {
+    return static_cast<RendererState::Type>(tnRendererState.GetInteger());
+  }
+  else if(tnRendererState.GetType() == TreeNode::STRING)
+  {
+    return RendererState::Parse(tnRendererState.GetString());
+  }
+  else
+  {
+    return -1;
+  }
+}
+
+///@brief Reads arc properties.
+void ReadArcField(const TreeNode* eArc, ArcRenderable& arc)
+{
+  ReadBool(eArc->GetChild("antiAliasing"), arc.mAntiAliasing);
+  ReadInt(eArc->GetChild("arcCaps"), arc.mArcCaps);
+  ReadFloat(eArc->GetChild("radius"), arc.mRadius);
+
+  arc.mStartAngleDegrees = .0f;
+  ReadFloat(eArc->GetChild("startAngle"), arc.mStartAngleDegrees);
+
+  arc.mEndAngleDegrees = .0f;
+  ReadFloat(eArc->GetChild("endAngle"), arc.mEndAngleDegrees);
+}
+
+const TreeNode* GetNthChild(const TreeNode* node, uint32_t index)
+{
+  uint32_t i = 0;
+  for(TreeNode::ConstIterator it = (*node).CBegin(); it != (*node).CEnd(); ++it, ++i)
+  {
+    if(i == index)
+    {
+      return &((*it).second);
+    }
+  }
+  return NULL;
+}
+
+const TreeNode* RequireChild(const TreeNode* node, const std::string& childName)
+{
+  auto child = node->GetChild(childName);
+  if(!child)
+  {
+    ExceptionFlinger flinger(ASSERT_LOCATION);
+    flinger << "Failed to find child node '" << childName << "'";
+    if(auto nodeName = node->GetName())
+    {
+      flinger << " on '" << nodeName << "'";
+    }
+    flinger << ".";
+  }
+  return child;
+}
+
+void ParseProperties(const Toolkit::TreeNode& node, Property::Array& array);
+
+void ParseProperties(const Toolkit::TreeNode& node, Property::Map& map)
+{
+  DALI_ASSERT_DEBUG(node.GetType() == TreeNode::OBJECT);
+  for(auto i0 = node.CBegin(), i1 = node.CEnd(); i0 != i1; ++i0)
+  {
+    auto kv = *i0;
+    switch(kv.second.GetType())
+    {
+      case TreeNode::ARRAY:
+      {
+        Property::Array array;
+        ParseProperties(kv.second, array);
+        map.Insert(kv.first, array);
+        break;
+      }
+
+      case TreeNode::OBJECT:
+      {
+        Property::Map innerMap;
+        ParseProperties(kv.second, innerMap);
+        map.Insert(kv.first, innerMap);
+        break;
+      }
+
+      case TreeNode::STRING:
+      {
+        map.Insert(kv.first, kv.second.GetString());
+        break;
+      }
+
+      case TreeNode::INTEGER:
+      {
+        map.Insert(kv.first, kv.second.GetInteger());
+        break;
+      }
+
+      case TreeNode::BOOLEAN:
+      {
+        map.Insert(kv.first, kv.second.GetBoolean());
+        break;
+      }
+
+      case TreeNode::FLOAT:
+      {
+        map.Insert(kv.first, kv.second.GetFloat());
+        break;
+      }
+
+      case TreeNode::IS_NULL:
+      {
+        break;
+      }
+    }
+  }
+}
+
+void ParseProperties(const Toolkit::TreeNode& node, Property::Array& array)
+{
+  DALI_ASSERT_DEBUG(node.GetType() == TreeNode::ARRAY);
+  for(auto i0 = node.CBegin(), i1 = node.CEnd(); i0 != i1; ++i0)
+  {
+    auto kv = *i0;
+    switch(kv.second.GetType())
+    {
+      case TreeNode::ARRAY:
+      {
+        Property::Array innerArray;
+        ParseProperties(kv.second, innerArray);
+        array.PushBack(innerArray);
+        break;
+      }
+
+      case TreeNode::OBJECT:
+      {
+        Property::Map map;
+        ParseProperties(kv.second, map);
+        array.PushBack(map);
+        break;
+      }
+
+      case TreeNode::STRING:
+      {
+        array.PushBack(kv.second.GetString());
+        break;
+      }
+
+      case TreeNode::INTEGER:
+      {
+        array.PushBack(kv.second.GetInteger());
+        break;
+      }
+
+      case TreeNode::BOOLEAN:
+      {
+        array.PushBack(kv.second.GetBoolean());
+        break;
+      }
+
+      case TreeNode::FLOAT:
+      {
+        array.PushBack(kv.second.GetFloat());
+        break;
+      }
+
+      case TreeNode::IS_NULL:
+      {
+        break;
+      }
+    }
+  }
+}
+
+} // namespace
+
+struct DliLoaderImpl::Impl
+{
+  StringCallback      mOnError = DefaultErrorCallback;
+  Toolkit::JsonParser mParser;
+
+  void ParseScene(LoadParams& params);
+
+private:
+  std::map<Index, Matrix> mInverseBindMatrices;
+
+  /**
+   * @brief Due to .dli nodes being processed in depth-first traversal with orphans being
+   *  ignored, features that rely on node indices (which is more compact and closer to
+   *  glTF) require a mapping from .dli node indices to those in the resulting SceneDefinition.
+   *  The index mapper is responsible for maintaing this mapping, and resolving node IDs
+   *  once the processing of the nodes has finished.
+   * @note The resolution requires the whole scene graph to finish parsing, therefore any
+   *  node extensions relying on node IDs will see the dli ID in their processor.
+   */
+  struct IIndexMapper
+  {
+    /**
+     * @brief Attempts to create a mapping from a node's @a dli index to its @a scene
+     *  index.
+     * @return Whether the operation was successful.
+     */
+    virtual bool Map(Index iDli, Index iScene) = 0;
+
+    /**
+     * @return The scene index for the node's @a dli index.
+     */
+    virtual Index Resolve(Index iDli) = 0;
+  };
+
+  /**
+   * @brief Traverses the DOM tree created by LoadDocument() in an attempt to create
+   *  an intermediate representation of resources and nodes.
+   */
+  void ParseSceneInternal(Index iScene, const Toolkit::TreeNode* tnScenes, const Toolkit::TreeNode* tnNodes, LoadParams& params);
+
+  void ParseSkeletons(const Toolkit::TreeNode* skeletons, Dali::Scene3D::Loader::SceneDefinition& scene, Dali::Scene3D::Loader::ResourceBundle& resources);
+  void ParseEnvironments(const Toolkit::TreeNode* environments, Dali::Scene3D::Loader::ResourceBundle& resources);
+  void ParseMaterials(const Toolkit::TreeNode* materials, DliInputParameter::ConvertColorCode convertColorCode, Dali::Scene3D::Loader::ResourceBundle& resources);
+
+  void ParseNodes(const Toolkit::TreeNode* nodes, Index index, LoadParams& params);
+  void ParseNodesInternal(const Toolkit::TreeNode* nodes, Index index, std::vector<Index>& inOutParentStack, LoadParams& params, IIndexMapper& indexMapper);
+
+  void ParseAnimations(const Toolkit::TreeNode* animations, LoadParams& params);
+  void ParseAnimationGroups(const Toolkit::TreeNode* animationGroups, LoadParams& params);
+
+  void ParseShaders(const Toolkit::TreeNode* shaders, Dali::Scene3D::Loader::ResourceBundle& resources);
+  void ParseMeshes(const Toolkit::TreeNode* meshes, Dali::Scene3D::Loader::ResourceBundle& resources);
+
+  void GetCameraParameters(std::vector<Dali::Scene3D::Loader::CameraParameters>& cameras) const;
+  void GetLightParameters(std::vector<Dali::Scene3D::Loader::LightParameters>& lights) const;
+};
+
+DliLoaderImpl::DliLoaderImpl()
+: mImpl{new Impl}
+{
+}
+
+DliLoaderImpl::~DliLoaderImpl() = default;
+
+void DliLoaderImpl::SetErrorCallback(StringCallback onError)
+{
+  mImpl->mOnError = onError;
+}
+
+bool DliLoaderImpl::LoadModel(const std::string& uri, Dali::Scene3D::Loader::LoadResult& result)
+{
+  std::string daliBuffer = LoadTextFile(uri.c_str());
+
+  auto& parser = mImpl->mParser;
+  parser       = JsonParser::New();
+  if(!parser.Parse(daliBuffer))
+  {
+    return false;
+  }
+
+  std::filesystem::path                    modelPath(uri);
+  Dali::Scene3D::Loader::DliInputParameter input;
+  LoadParams                               loadParams;
+  if(mInputParameter)
+  {
+    loadParams.input = static_cast<DliInputParameter*>(mInputParameter);
+  }
+  else
+  {
+    input.mAnimationsPath = std::string(modelPath.parent_path()) + "/";
+    loadParams.input      = &input;
+  }
+  loadParams.output = &result;
+
+  mImpl->ParseScene(loadParams);
+  return true;
+}
+
+std::string DliLoaderImpl::GetParseError() const
+{
+  std::stringstream stream;
+
+  auto& parser = mImpl->mParser;
+  if(parser.ParseError())
+  {
+    stream << "position: " << parser.GetErrorPosition() << ", line: " << parser.GetErrorLineNumber() << ", column: " << parser.GetErrorColumn() << ", description: " << parser.GetErrorDescription() << ".";
+  }
+
+  return stream.str();
+}
+
+void DliLoaderImpl::Impl::ParseScene(LoadParams& params)
+{
+  auto& input  = *params.input;
+  auto& output = *params.output;
+
+  // get index of root node.
+  auto docRoot = mParser.GetRoot();
+  if(docRoot)
+  {
+    // Process resources first - these are shared
+    if(auto environments = docRoot->GetChild("environment"))
+    {
+      ParseEnvironments(environments, output.mResources); // NOTE: must precede parsing of materials
+    }
+
+    if(auto meshes = docRoot->GetChild("meshes"))
+    {
+      ParseMeshes(meshes, output.mResources);
+    }
+
+    if(auto shaders = docRoot->GetChild("shaders"))
+    {
+      ParseShaders(shaders, output.mResources);
+    }
+
+    if(auto materials = docRoot->GetChild("materials"))
+    {
+      ParseMaterials(materials, input.mConvertColorCode, output.mResources);
+    }
+
+    for(auto& c : input.mPreNodeCategoryProcessors)
+    {
+      if(auto node = docRoot->GetChild(c.first))
+      {
+        Property::Array array;
+        ParseProperties(*node, array);
+        c.second(std::move(array), mOnError);
+      }
+    }
+
+    // Process scenes
+    Index iScene = 0; // default scene
+    ReadIndex(docRoot->GetChild("scene"), iScene);
+
+    auto tnScenes = RequireChild(docRoot, "scenes");
+    auto tnNodes  = RequireChild(docRoot, "nodes");
+    ParseSceneInternal(iScene, tnScenes, tnNodes, params);
+
+    ParseSkeletons(docRoot->GetChild("skeletons"), output.mScene, output.mResources);
+
+    output.mScene.EnsureUniqueSkinningShaderInstances(output.mResources);
+    output.mScene.EnsureUniqueBlendShapeShaderInstances(output.mResources);
+
+    // Ger cameras and lights
+    GetCameraParameters(output.mCameraParameters);
+    GetLightParameters(output.mLightParameters);
+
+    // Post-node processors and animations last
+    for(auto& c : input.mPostNodeCategoryProcessors)
+    {
+      if(auto node = docRoot->GetChild(c.first))
+      {
+        Property::Array array;
+        ParseProperties(*node, array);
+        c.second(std::move(array), mOnError);
+      }
+    }
+
+    if(auto animations = docRoot->GetChild("animations"))
+    {
+      ParseAnimations(animations, params);
+    }
+
+    if(!output.mAnimationDefinitions.empty())
+    {
+      if(auto animationGroups = docRoot->GetChild("animationGroups"))
+      {
+        ParseAnimationGroups(animationGroups, params);
+      }
+    }
+  }
+}
+
+void DliLoaderImpl::Impl::ParseSceneInternal(Index iScene, const Toolkit::TreeNode* tnScenes, const Toolkit::TreeNode* tnNodes, LoadParams& params)
+{
+  auto getSceneRootIdx = [tnScenes, tnNodes](Index iScene)
+  {
+    auto tn = GetNthChild(tnScenes, iScene); // now a "scene" object
+    if(!tn)
+    {
+      ExceptionFlinger(ASSERT_LOCATION) << iScene << " is out of bounds access into " << SCENES << ".";
+    }
+
+    tn = RequireChild(tn, NODES); // now a "nodes" array
+    if(tn->GetType() != TreeNode::ARRAY)
+    {
+      ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " has an invalid type; array required.";
+    }
+
+    if(tn->Size() < 1)
+    {
+      ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " must define a node id.";
+    }
+
+    tn = GetNthChild(tn, 0); // now the first element of the array
+    Index iRootNode;
+    if(!ReadIndex(tn, iRootNode))
+    {
+      ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " has an invalid value for root node index: '" << iRootNode << "'.";
+    }
+
+    if(iRootNode >= tnNodes->Size())
+    {
+      ExceptionFlinger(ASSERT_LOCATION) << "Root node index << " << iRootNode << " of scene " << iScene << " is out of bounds.";
+    }
+
+    tn = GetNthChild(tnNodes, iRootNode); // now a "node" object
+    if(tn->GetType() != TreeNode::OBJECT)
+    {
+      ExceptionFlinger(ASSERT_LOCATION) << "Root node of scene " << iScene << " is of invalid JSON type; object required";
+    }
+
+    return iRootNode;
+  };
+
+  Index iRootNode = getSceneRootIdx(iScene);
+  ParseNodes(tnNodes, iRootNode, params);
+
+  auto& scene = params.output->mScene;
+  scene.AddRootNode(0);
+
+  for(Index i = 0; i < iScene; ++i)
+  {
+    Index       iRootNode = getSceneRootIdx(i);
+    const Index iRoot     = scene.GetNodeCount();
+    ParseNodes(tnNodes, iRootNode, params);
+    scene.AddRootNode(iRoot);
+  }
+
+  auto numScenes = tnScenes->Size();
+  for(Index i = iScene + 1; i < numScenes; ++i)
+  {
+    Index       iRootNode = getSceneRootIdx(i);
+    const Index iRoot     = scene.GetNodeCount();
+    ParseNodes(tnNodes, iRootNode, params);
+    scene.AddRootNode(iRoot);
+  }
+}
+
+void DliLoaderImpl::Impl::ParseSkeletons(const TreeNode* skeletons, Dali::Scene3D::Loader::SceneDefinition& scene, Dali::Scene3D::Loader::ResourceBundle& resources)
+{
+  if(skeletons)
+  {
+    auto iStart = skeletons->CBegin();
+    for(auto i0 = iStart, i1 = skeletons->CEnd(); i0 != i1; ++i0)
+    {
+      auto&       node = (*i0).second;
+      std::string skeletonRootName;
+      if(ReadString(node.GetChild(NODE), skeletonRootName))
+      {
+        SkeletonDefinition skeleton;
+        if(!scene.FindNode(skeletonRootName, &skeleton.mRootNodeIdx))
+        {
+          ExceptionFlinger(ASSERT_LOCATION) << FormatString("Skeleton %d: node '%s' not defined.", resources.mSkeletons.size(), skeletonRootName.c_str());
+        }
+
+        uint32_t                   jointCount = 0;
+        std::function<void(Index)> visitFn;
+        auto&                      ibms = mInverseBindMatrices;
+        visitFn                         = [&](Index id)
+        {
+          auto node = scene.GetNode(id);
+          jointCount += ibms.find(id) != ibms.end();
+
+          for(auto i : node->mChildren)
+          {
+            visitFn(i);
+          }
+        };
+        visitFn(skeleton.mRootNodeIdx);
+
+        if(jointCount > Skinning::MAX_JOINTS)
+        {
+          mOnError(FormatString("Skeleton %d: joint count exceeds supported limit.", resources.mSkeletons.size()));
+          jointCount = Skinning::MAX_JOINTS;
+        }
+
+        skeleton.mJoints.reserve(jointCount);
+
+        visitFn = [&](Index id)
+        {
+          auto iFind = ibms.find(id);
+          if(iFind != ibms.end() && skeleton.mJoints.size() < Skinning::MAX_JOINTS)
+          {
+            skeleton.mJoints.push_back({id, iFind->second});
+          }
+
+          auto node = scene.GetNode(id);
+          for(auto i : node->mChildren)
+          {
+            visitFn(i);
+          }
+        };
+        visitFn(skeleton.mRootNodeIdx);
+
+        resources.mSkeletons.push_back(std::move(skeleton));
+      }
+      else
+      {
+        ExceptionFlinger(ASSERT_LOCATION) << "skeleton " << std::distance(iStart, i0) << ": Missing required attribute '" << NODE << "'.";
+      }
+    }
+  }
+}
+
+void DliLoaderImpl::Impl::ParseEnvironments(const TreeNode* environments, Dali::Scene3D::Loader::ResourceBundle& resources)
+{
+  Matrix cubeOrientation(Matrix::IDENTITY);
+
+  for(auto i0 = environments->CBegin(), i1 = environments->CEnd(); i0 != i1; ++i0)
+  {
+    auto& node = (*i0).second;
+
+    EnvironmentDefinition envDef;
+    ReadString(node.GetChild("cubeSpecular"), envDef.mSpecularMapPath);
+    ReadString(node.GetChild("cubeDiffuse"), envDef.mDiffuseMapPath);
+    ToUnixFileSeparators(envDef.mSpecularMapPath);
+    ToUnixFileSeparators(envDef.mDiffuseMapPath);
+    envDef.mIblIntensity = 1.0f;
+    ReadFloat(node.GetChild("iblIntensity"), envDef.mIblIntensity);
+    if(ReadVector(node.GetChild("cubeInitialOrientation"), cubeOrientation.AsFloat(), 16u))
+    {
+      envDef.mCubeOrientation = Quaternion(cubeOrientation);
+    }
+
+    resources.mEnvironmentMaps.emplace_back(std::move(envDef), EnvironmentDefinition::Textures());
+  }
+
+  // NOTE: guarantees environmentMaps to have an empty environment.
+  if(resources.mEnvironmentMaps.empty())
+  {
+    resources.mEnvironmentMaps.emplace_back(EnvironmentDefinition(), EnvironmentDefinition::Textures());
+  }
+}
+
+void DliLoaderImpl::Impl::ParseShaders(const TreeNode* shaders, Dali::Scene3D::Loader::ResourceBundle& resources)
+{
+  uint32_t iShader = 0;
+  for(auto i0 = shaders->CBegin(), i1 = shaders->CEnd(); i0 != i1; ++i0, ++iShader)
+  {
+    auto&            node = (*i0).second;
+    ShaderDefinition shaderDef;
+    ReadStringVector(node.GetChild("defines"), shaderDef.mDefines);
+
+    // Read shader hints. Possible values are:
+    //                         Don't define for No hints.
+    // "OUTPUT_IS_TRANSPARENT" Might generate transparent alpha from opaque inputs.
+    //     "MODIFIES_GEOMETRY" Might change position of vertices, this option disables any culling optimizations.
+
+    ReadStringVector(node.GetChild(HINTS), shaderDef.mHints);
+
+    if(ReadString(node.GetChild("vertex"), shaderDef.mVertexShaderPath) &&
+       ReadString(node.GetChild("fragment"), shaderDef.mFragmentShaderPath))
+    {
+      ToUnixFileSeparators(shaderDef.mVertexShaderPath);
+      ToUnixFileSeparators(shaderDef.mFragmentShaderPath);
+
+      for(TreeNode::ConstIterator j0 = node.CBegin(), j1 = node.CEnd(); j0 != j1; ++j0)
+      {
+        const TreeNode::KeyNodePair& keyValue = *j0;
+        const std::string&           key      = keyValue.first;
+        const TreeNode&              value    = keyValue.second;
+
+        Property::Value uniformValue;
+        if(key.compare("vertex") == 0 || key.compare("fragment") == 0 || key.compare("defines") == 0 || key.compare(HINTS) == 0)
+        {
+          continue;
+        }
+        else if(key.compare("rendererState") == 0)
+        {
+          shaderDef.mRendererState = ReadRendererState(keyValue.second);
+        }
+        else if(value.GetType() == TreeNode::INTEGER || value.GetType() == TreeNode::FLOAT)
+        {
+          float f = 0.f;
+          ReadFloat(&value, f);
+          uniformValue = f;
+        }
+        else if(value.GetType() == TreeNode::BOOLEAN)
+        {
+          DALI_LOG_WARNING("\"bool\" uniforms are handled as floats in shader");
+          bool value = false;
+          if(ReadBool(&keyValue.second, value))
+          {
+            uniformValue = value ? 1.0f : 0.0f;
+          }
+        }
+        else
+          switch(auto size = GetNumericalArraySize(&value))
+          {
+            case 16:
+            {
+              Matrix m;
+              ReadVector(&value, m.AsFloat(), size);
+              uniformValue = m;
+              break;
+            }
+
+            case 9:
+            {
+              Matrix3 m;
+              ReadVector(&value, m.AsFloat(), size);
+              uniformValue = m;
+              break;
+            }
+
+            case 4:
+            {
+              Vector4 v;
+              ReadVector(&value, v.AsFloat(), size);
+              uniformValue = v;
+              break;
+            }
+
+            case 3:
+            {
+              Vector3 v;
+              ReadVector(&value, v.AsFloat(), size);
+              uniformValue = v;
+              break;
+            }
+
+            case 2:
+            {
+              Vector2 v;
+              ReadVector(&value, v.AsFloat(), size);
+              uniformValue = v;
+              break;
+            }
+
+            default:
+              mOnError(FormatString(
+                "shader %u: Ignoring uniform '%s': failed to infer type from %zu elements.",
+                iShader,
+                key.c_str(),
+                size));
+              break;
+          }
+
+        if(Property::NONE != uniformValue.GetType())
+        {
+          shaderDef.mUniforms.Insert(key, uniformValue);
+        }
+      }
+
+      resources.mShaders.emplace_back(std::move(shaderDef), Shader());
+    }
+    else
+    {
+      ExceptionFlinger(ASSERT_LOCATION) << "shader " << iShader << ": Missing vertex / fragment shader definition.";
+    }
+  }
+}
+
+void DliLoaderImpl::Impl::ParseMeshes(const TreeNode* meshes, Dali::Scene3D::Loader::ResourceBundle& resources)
+{
+  for(auto i0 = meshes->CBegin(), i1 = meshes->CEnd(); i0 != i1; ++i0)
+  {
+    auto& node = (*i0).second;
+
+    MeshDefinition meshDef;
+    if(!ReadString(node.GetChild(URI), meshDef.mUri))
+    {
+      ExceptionFlinger(ASSERT_LOCATION) << "mesh " << resources.mMeshes.size() << ": Missing required attribute '" << URI << "'.";
+    }
+
+    ToUnixFileSeparators(meshDef.mUri);
+
+    std::string primitive;
+    if(ReadString(node.GetChild("primitive"), primitive))
+    {
+      if(primitive == "LINES")
+      {
+        meshDef.mPrimitiveType = Geometry::LINES;
+      }
+      else if(primitive == "POINTS")
+      {
+        meshDef.mPrimitiveType = Geometry::POINTS;
+      }
+      else if(primitive != "TRIANGLES")
+      {
+        mOnError(FormatString(
+          "mesh %d: Using TRIANGLES instead of unsupported primitive type '%s'.",
+          resources.mMeshes.size(),
+          primitive.c_str()));
+      }
+    }
+
+    int attributes;
+    if(ReadInt(node.GetChild("attributes"), attributes))
+    {
+      if(MaskMatch(attributes, MeshDefinition::INDICES) &&
+         !ReadAttribAccessor(node.GetChild("indices"), meshDef.mIndices))
+      {
+        ExceptionFlinger(ASSERT_LOCATION) << FormatString("mesh %d: Failed to read %s.",
+                                                          resources.mMeshes.size(),
+                                                          "indices");
+      }
+
+      if(MaskMatch(attributes, MeshDefinition::POSITIONS) &&
+         !ReadAttribAccessor(node.GetChild("positions"), meshDef.mPositions))
+      {
+        ExceptionFlinger(ASSERT_LOCATION) << FormatString("mesh %d: Failed to read %s.",
+                                                          resources.mMeshes.size(),
+                                                          "positions");
+      }
+
+      if(MaskMatch(attributes, MeshDefinition::NORMALS) &&
+         !ReadAttribAccessor(node.GetChild("normals"), meshDef.mNormals))
+      {
+        mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "normals"));
+      }
+
+      if(MaskMatch(attributes, MeshDefinition::TEX_COORDS) &&
+         !ReadAttribAccessor(node.GetChild("textures"), meshDef.mTexCoords))
+      {
+        mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "textures"));
+      }
+
+      if(MaskMatch(attributes, MeshDefinition::TANGENTS) &&
+         !ReadAttribAccessor(node.GetChild("tangents"), meshDef.mTangents))
+      {
+        mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "tangents"));
+      }
+
+      // NOTE: we're no longer reading bitangents as these are calculated in the shaders.
+      if(ReadIndex(node.GetChild("skeleton"), meshDef.mSkeletonIdx))
+      {
+        if(!MaskMatch(attributes, MeshDefinition::JOINTS_0) &&
+           !MaskMatch(attributes, MeshDefinition::WEIGHTS_0))
+        {
+          mOnError(FormatString("mesh %d: Expected joints0 / weights0 attribute(s) missing.",
+                                resources.mMeshes.size()));
+        }
+        else if(!ReadAttribAccessor(node.GetChild("joints0"), meshDef.mJoints0) ||
+                !ReadAttribAccessor(node.GetChild("weights0"), meshDef.mWeights0))
+        {
+          mOnError(FormatString("mesh %d: Failed to read skinning information.",
+                                resources.mMeshes.size()));
+        }
+      }
+
+      if(auto blendshapeHeader = node.GetChild(BLEND_SHAPE_HEADER))
+      {
+        std::string blendShapeVersion;
+        ReadString(blendshapeHeader->GetChild(VERSION), blendShapeVersion);
+
+        if(0u == blendShapeVersion.compare(BLEND_SHAPE_VERSION_1_0))
+        {
+          meshDef.mBlendShapeVersion = BlendShapes::Version::VERSION_1_0;
+        }
+        else if(0u == blendShapeVersion.compare(BLEND_SHAPE_VERSION_2_0))
+        {
+          meshDef.mBlendShapeVersion = BlendShapes::Version::VERSION_2_0;
+        }
+
+        switch(meshDef.mBlendShapeVersion)
+        {
+          case BlendShapes::Version::VERSION_1_0:
+          case BlendShapes::Version::VERSION_2_0: // FALL THROUGH
+          {
+            ReadAttribBlob(blendshapeHeader, meshDef.mBlendShapeHeader);
+            break;
+          }
+          default:
+          {
+            // nothing to do
+            break;
+          }
+        }
+      }
+
+      if(auto blendShapes = node.GetChild(BLEND_SHAPES))
+      {
+        meshDef.mBlendShapes.resize(blendShapes->Size());
+
+        auto index = 0u;
+        for(auto it = blendShapes->CBegin(), endIt = blendShapes->CEnd(); it != endIt; ++it, ++index)
+        {
+          // Each blend shape is stored as the difference with the original mesh.
+
+          auto& blendShapeNode = (*it).second;
+
+          auto& blendShape = meshDef.mBlendShapes[index];
+          ReadString(blendShapeNode.GetChild("name"), blendShape.name);
+          if(auto position = blendShapeNode.GetChild("positions"))
+          {
+            ReadAttribAccessor(position, blendShape.deltas);
+          }
+          if(auto normals = blendShapeNode.GetChild("normals"))
+          {
+            ReadAttribAccessor(normals, blendShape.normals);
+          }
+          if(auto tangents = blendShapeNode.GetChild("tangents"))
+          {
+            ReadAttribAccessor(tangents, blendShape.tangents);
+          }
+          ReadFloat(blendShapeNode.GetChild("weight"), blendShape.weight);
+        }
+      }
+
+      bool flipV;
+      if(ReadBool(node.GetChild("flipV"), flipV))
+      {
+        meshDef.mFlags |= flipV * MeshDefinition::FLIP_UVS_VERTICAL;
+      }
+
+      resources.mMeshes.emplace_back(std::move(meshDef), MeshGeometry());
+    }
+  }
+}
+
+void DliLoaderImpl::Impl::ParseMaterials(const TreeNode* materials, DliInputParameter::ConvertColorCode convertColorCode, Dali::Scene3D::Loader::ResourceBundle& resources)
+{
+  for(auto i0 = materials->CBegin(), i1 = materials->CEnd(); i0 != i1; ++i0)
+  {
+    auto& node = (*i0).second;
+
+    MaterialDefinition materialDef;
+    if(auto eEnvironment = node.GetChild("environment"))
+    {
+      ReadIndex(eEnvironment, materialDef.mEnvironmentIdx);
+      if(static_cast<unsigned int>(materialDef.mEnvironmentIdx) >= resources.mEnvironmentMaps.size())
+      {
+        ExceptionFlinger(ASSERT_LOCATION) << "material " << resources.mMaterials.size() << ": Environment index " << materialDef.mEnvironmentIdx << " out of bounds (" << resources.mEnvironmentMaps.size() << ").";
+      }
+    }
+
+    // TODO : need to consider AGIF
+    std::vector<std::string> texturePaths;
+    std::string              texturePath;
+    if(ReadString(node.GetChild("albedoMap"), texturePath))
+    {
+      ToUnixFileSeparators(texturePath);
+      const auto semantic = MaterialDefinition::ALBEDO;
+      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
+      materialDef.mFlags |= semantic | MaterialDefinition::TRANSPARENCY; // NOTE: only in dli does single / separate ALBEDO texture mean TRANSPARENCY.
+    }
+    if(ReadString(node.GetChild("albedoMetallicMap"), texturePath))
+    {
+      ToUnixFileSeparators(texturePath);
+
+      if(MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO))
+      {
+        mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "albedo"));
+      }
+
+      const auto semantic = MaterialDefinition::ALBEDO | MaterialDefinition::METALLIC;
+      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
+      materialDef.mFlags |= semantic;
+    }
+
+    if(ReadString(node.GetChild("metallicRoughnessMap"), texturePath))
+    {
+      ToUnixFileSeparators(texturePath);
+
+      if(MaskMatch(materialDef.mFlags, MaterialDefinition::METALLIC))
+      {
+        mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "metallic"));
+      }
+
+      const auto semantic = MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS;
+      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
+      materialDef.mFlags |= semantic |
+                            // We have a metallic-roughhness map and the first texture did not have albedo semantics - we're in the transparency workflow.
+                            (MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO) * MaterialDefinition::TRANSPARENCY);
+    }
+
+    if(ReadString(node.GetChild("normalMap"), texturePath))
+    {
+      ToUnixFileSeparators(texturePath);
+
+      const auto semantic = MaterialDefinition::NORMAL;
+      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
+      materialDef.mFlags |= semantic |
+                            // We have a standalone normal map and the first texture did not have albedo semantics - we're in the transparency workflow.
+                            (MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO) * MaterialDefinition::TRANSPARENCY);
+    }
+
+    if(ReadString(node.GetChild("normalRoughnessMap"), texturePath))
+    {
+      ToUnixFileSeparators(texturePath);
+
+      if(MaskMatch(materialDef.mFlags, MaterialDefinition::NORMAL))
+      {
+        mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "normal"));
+      }
+
+      if(MaskMatch(materialDef.mFlags, MaterialDefinition::ROUGHNESS))
+      {
+        mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "roughness"));
+      }
+
+      if(MaskMatch(materialDef.mFlags, MaterialDefinition::TRANSPARENCY))
+      {
+        mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "transparency"));
+      }
+
+      const auto semantic = MaterialDefinition::NORMAL | MaterialDefinition::ROUGHNESS;
+      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
+      materialDef.mFlags |= semantic;
+    }
+
+    if(ReadString(node.GetChild("subsurfaceMap"), texturePath))
+    {
+      ToUnixFileSeparators(texturePath);
+
+      const auto semantic = MaterialDefinition::SUBSURFACE;
+      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
+      materialDef.mFlags |= semantic;
+    }
+
+    if(ReadString(node.GetChild("occlusionMap"), texturePath))
+    {
+      ToUnixFileSeparators(texturePath);
+      const auto semantic = MaterialDefinition::OCCLUSION;
+      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
+      materialDef.mFlags |= semantic;
+    }
+
+    if(ReadColorCodeOrColor(&node, materialDef.mColor, convertColorCode) &&
+       materialDef.mColor.a < 1.0f)
+    {
+      materialDef.mFlags |= MaterialDefinition::TRANSPARENCY;
+    }
+
+    ReadFloat(node.GetChild("metallic"), materialDef.mMetallic);
+    ReadFloat(node.GetChild("roughness"), materialDef.mRoughness);
+
+    bool mipmaps;
+    if(ReadBool(node.GetChild("mipmap"), mipmaps) && mipmaps)
+    {
+      for(auto& ts : materialDef.mTextureStages)
+      {
+        ts.mTexture.mSamplerFlags |= SamplerFlags::FILTER_MIPMAP_LINEAR;
+      }
+    }
+
+    resources.mMaterials.emplace_back(std::move(materialDef), TextureSet());
+  }
+}
+
+void DliLoaderImpl::Impl::ParseNodes(const TreeNode* const nodes, Index index, LoadParams& params)
+{
+  std::vector<Index> parents;
+  parents.reserve(8);
+
+  struct IndexMapper : IIndexMapper
+  {
+    IndexMapper(size_t numNodes)
+    {
+      mIndices.reserve(numNodes);
+    }
+
+    virtual bool Map(Index iDli, Index iScene) override
+    {
+      Entry idx{iDli, iScene};
+      auto  iInsert = std::lower_bound(mIndices.begin(), mIndices.end(), idx);
+      if(iInsert == mIndices.end() || iInsert->iDli != iDli)
+      {
+        mIndices.insert(iInsert, idx);
+      }
+      else if(iInsert->iScene != iScene)
+      {
+        return false;
+      }
+      return true;
+    }
+
+    virtual unsigned int Resolve(Index iDli) override
+    {
+      auto iFind = std::lower_bound(mIndices.begin(), mIndices.end(), iDli, [](const Entry& idx, Index iDli)
+                                    { return idx.iDli < iDli; });
+      DALI_ASSERT_ALWAYS(iFind != mIndices.end());
+      return iFind->iScene;
+    }
+
+  private:
+    struct Entry
+    {
+      unsigned int iDli;
+      unsigned int iScene;
+
+      bool operator<(const Entry& other) const
+      {
+        return iDli < other.iDli;
+      }
+    };
+    std::vector<Entry> mIndices;
+  } mapper(nodes->Size());
+  ParseNodesInternal(nodes, index, parents, params, mapper);
+
+  auto& scene = params.output->mScene;
+  for(size_t i0 = 0, i1 = scene.GetNodeCount(); i0 < i1; ++i0)
+  {
+    for(auto& c : scene.GetNode(i0)->mConstraints)
+    {
+      c.mSourceIdx = mapper.Resolve(c.mSourceIdx);
+    }
+  }
+}
+
+void DliLoaderImpl::Impl::ParseNodesInternal(const TreeNode* const nodes, Index index, std::vector<Index>& inOutParentStack, LoadParams& params, IIndexMapper& mapper)
+{
+  // Properties that may be resolved from a JSON value with ReadInt() -- or default to 0.
+  struct IndexProperty
+  {
+    ResourceType::Value type;
+    const TreeNode*     source;
+    Index&              target;
+  };
+  std::vector<IndexProperty> resourceIds;
+  resourceIds.reserve(4);
+
+  if(auto node = GetNthChild(nodes, index))
+  {
+    Dali::Scene3D::Loader::NodeDefinition nodeDef;
+    nodeDef.mParentIdx = inOutParentStack.empty() ? INVALID_INDEX : inOutParentStack.back();
+
+    // name
+    ReadString(node->GetChild(NAME), nodeDef.mName);
+
+    // transform
+    ReadModelTransform(node, nodeDef.mOrientation, nodeDef.mPosition, nodeDef.mScale);
+
+    // Reads the size of the node.
+    //
+    // * It can be given as 'size' or 'bounds'.
+    // * The sdk saves the 'size' as a vector2 in some cases.
+    // * To avoid size related issues the following code attemps
+    //   to read the 'size/bounds' as a vector3 first, if it's
+    //   not successful then reads it as a vector2.
+    ReadVector(node->GetChild("size"), nodeDef.mSize.AsFloat(), 3) ||
+      ReadVector(node->GetChild("size"), nodeDef.mSize.AsFloat(), 2) ||
+      ReadVector(node->GetChild("bounds"), nodeDef.mSize.AsFloat(), 3) ||
+      ReadVector(node->GetChild("bounds"), nodeDef.mSize.AsFloat(), 2);
+
+    // visibility
+    ReadBool(node->GetChild("visible"), nodeDef.mIsVisible);
+
+    // type classification
+    if(auto eCustomization = node->GetChild("customization")) // customization
+    {
+      std::string tag;
+      if(ReadString(eCustomization->GetChild("tag"), tag))
+      {
+        nodeDef.mCustomization.reset(new Dali::Scene3D::Loader::NodeDefinition::CustomizationDefinition{tag});
+      }
+    }
+    else // something renderable maybe
+    {
+      std::unique_ptr<Dali::Scene3D::Loader::NodeDefinition::Renderable> renderable;
+      ModelRenderable*                                                   modelRenderable = nullptr; // no ownership, aliasing renderable for the right type.
+
+      const TreeNode* eRenderable = nullptr;
+      if((eRenderable = node->GetChild("model")))
+      {
+        // check for mesh before allocating - this can't be missing.
+        auto eMesh = eRenderable->GetChild("mesh");
+        if(!eMesh)
+        {
+          ExceptionFlinger(ASSERT_LOCATION) << "node " << nodeDef.mName << ": Missing mesh definition.";
+        }
+
+        modelRenderable = new ModelRenderable();
+        renderable.reset(modelRenderable);
+
+        resourceIds.push_back({ResourceType::Mesh, eMesh, modelRenderable->mMeshIdx});
+      }
+      else if((eRenderable = node->GetChild("arc")))
+      {
+        // check for mesh before allocating - this can't be missing.
+        auto eMesh = eRenderable->GetChild("mesh");
+        if(!eMesh)
+        {
+          ExceptionFlinger(ASSERT_LOCATION) << "node " << nodeDef.mName << ": Missing mesh definition.";
+        }
+
+        auto arcRenderable = new ArcRenderable;
+        renderable.reset(arcRenderable);
+        modelRenderable = arcRenderable;
+
+        resourceIds.push_back({ResourceType::Mesh, eMesh, arcRenderable->mMeshIdx});
+
+        ReadArcField(eRenderable, *arcRenderable);
+      }
+
+      if(renderable && eRenderable != nullptr) // process common properties of all renderables + register payload
+      {
+        // shader
+        renderable->mShaderIdx = 0;
+        auto eShader           = eRenderable->GetChild("shader");
+        if(eShader)
+        {
+          resourceIds.push_back({ResourceType::Shader, eShader, renderable->mShaderIdx});
+        }
+
+        // color
+        if(modelRenderable)
+        {
+          modelRenderable->mMaterialIdx = 0; // must offer default of 0
+          auto eMaterial                = eRenderable->GetChild("material");
+          if(eMaterial)
+          {
+            resourceIds.push_back({ResourceType::Material, eMaterial, modelRenderable->mMaterialIdx});
+          }
+
+          if(!ReadColorCodeOrColor(eRenderable, modelRenderable->mColor, params.input->mConvertColorCode))
+          {
+            ReadColorCodeOrColor(node, modelRenderable->mColor, params.input->mConvertColorCode);
+          }
+        }
+
+        nodeDef.mRenderables.push_back(std::move(renderable));
+      }
+    }
+
+    // Resolve ints - default to 0 if undefined
+    auto& output = params.output;
+    for(auto& idRes : resourceIds)
+    {
+      Index iCheck = 0;
+      switch(idRes.type)
+      {
+        case ResourceType::Shader:
+          iCheck = output->mResources.mShaders.size();
+          break;
+
+        case ResourceType::Mesh:
+          iCheck = output->mResources.mMeshes.size();
+          break;
+
+        case ResourceType::Material:
+          iCheck = output->mResources.mMaterials.size();
+          break;
+
+        default:
+          ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": Invalid resource type: " << idRes.type << " (Programmer error)";
+      }
+
+      if(!idRes.source)
+      {
+        idRes.target = 0;
+      }
+      else if(idRes.source->GetType() != TreeNode::INTEGER)
+      {
+        ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": Invalid " << GetResourceTypeName(idRes.type) << " index type.";
+      }
+      else
+      {
+        idRes.target = idRes.source->GetInteger();
+      }
+
+      if(idRes.target >= iCheck)
+      {
+        ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": " << GetResourceTypeName(idRes.type) << " index " << idRes.target << " out of bounds (" << iCheck << ").";
+      }
+    }
+    resourceIds.clear();
+
+    // Extra properties
+    if(auto eExtras = node->GetChild("extras"))
+    {
+      auto& extras = nodeDef.mExtras;
+      extras.reserve(eExtras->Size());
+
+      for(auto i0 = eExtras->CBegin(), i1 = eExtras->CEnd(); i0 != i1; ++i0)
+      {
+        Dali::Scene3D::Loader::NodeDefinition::Extra e;
+
+        auto eExtra = *i0;
+        e.mKey      = eExtra.first;
+        if(e.mKey.empty())
+        {
+          mOnError(FormatString("node %d: empty string is invalid for name of extra %d; ignored.",
+                                index,
+                                extras.size()));
+          continue;
+        }
+
+        e.mValue = ReadPropertyValue(eExtra.second);
+        if(e.mValue.GetType() == Property::Type::NONE)
+        {
+          mOnError(FormatString("node %d: failed to interpret value of extra '%s' : %s; ignored.",
+                                index,
+                                e.mKey.c_str(),
+                                eExtra.second.GetString()));
+        }
+        else
+        {
+          auto iInsert = std::lower_bound(extras.begin(), extras.end(), e);
+          if(iInsert != extras.end() && iInsert->mKey == e.mKey)
+          {
+            mOnError(FormatString("node %d: extra '%s' already defined; overriding with %s.",
+                                  index,
+                                  e.mKey.c_str(),
+                                  eExtra.second.GetString()));
+            *iInsert = std::move(e);
+          }
+          else
+          {
+            extras.insert(iInsert, e);
+          }
+        }
+      }
+    }
+
+    // Constraints
+    if(auto eConstraints = node->GetChild("constraints"))
+    {
+      auto& constraints = nodeDef.mConstraints;
+      constraints.reserve(eConstraints->Size());
+
+      ConstraintDefinition cDef;
+      for(auto i0 = eConstraints->CBegin(), i1 = eConstraints->CEnd(); i0 != i1; ++i0)
+      {
+        auto eConstraint = *i0;
+        if(!ReadIndex(&eConstraint.second, cDef.mSourceIdx))
+        {
+          mOnError(FormatString("node %d: node ID %s for constraint %d is invalid; ignored.",
+                                index,
+                                eConstraint.second.GetString(),
+                                constraints.size()));
+        }
+        else
+        {
+          cDef.mProperty = eConstraint.first;
+
+          auto iInsert = std::lower_bound(constraints.begin(), constraints.end(), cDef);
+          if(iInsert != constraints.end() && *iInsert == cDef)
+          {
+            mOnError(FormatString("node %d: constraint %s@%d already defined; ignoring.",
+                                  index,
+                                  cDef.mProperty.c_str(),
+                                  cDef.mSourceIdx));
+          }
+          else
+          {
+            constraints.insert(iInsert, cDef);
+          }
+        }
+      }
+    }
+
+    // Determine index for mapping
+    const unsigned int myIndex = output->mScene.GetNodeCount();
+    if(!mapper.Map(index, myIndex))
+    {
+      mOnError(FormatString("node %d: error mapping dli index %d: node has multiple parents. Ignoring subtree.", index, myIndex));
+      return;
+    }
+
+    // if the node is a bone in a skeletal animation, it will have the inverse bind pose matrix.
+    Matrix invBindMatrix{false};
+    if(ReadVector(node->GetChild("inverseBindPoseMatrix"), invBindMatrix.AsFloat(), 16u)) // TODO: more robust error checking?
+    {
+      mInverseBindMatrices[myIndex] = invBindMatrix;
+    }
+
+    // Register nodeDef
+    auto rawDef = output->mScene.AddNode(std::make_unique<Dali::Scene3D::Loader::NodeDefinition>(std::move(nodeDef)));
+    if(rawDef) // NOTE: no ownership. Guaranteed to stay in scope.
+    {
+      // ...And only then parse children.
+      if(auto children = node->GetChild("children"))
+      {
+        inOutParentStack.push_back(myIndex);
+
+        rawDef->mChildren.reserve(children->Size());
+
+        uint32_t iChild = 0;
+        for(auto j0 = children->CBegin(), j1 = children->CEnd(); j0 != j1; ++j0, ++iChild)
+        {
+          auto& child = (*j0).second;
+          if(child.GetType() == TreeNode::INTEGER)
+          {
+            ParseNodesInternal(nodes, child.GetInteger(), inOutParentStack, params, mapper); // child object is created in scene definition.
+          }
+          else
+          {
+            ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ", child " << iChild << ": invalid index type.";
+          }
+        }
+
+        inOutParentStack.pop_back();
+      }
+      else if(rawDef->mCustomization)
+      {
+        mOnError(FormatString("node %d: not an actual customization without children.", index));
+      }
+
+      if(auto proc = params.input->mNodePropertyProcessor) // optional processing
+      {
+        // WARNING: constraint IDs are not resolved at this point.
+        Property::Map nodeData;
+        ParseProperties(*node, nodeData);
+        proc(*rawDef, std::move(nodeData), mOnError);
+      }
+    }
+    else
+    {
+      ExceptionFlinger(ASSERT_LOCATION) << "Node " << index << ": name already used.";
+    }
+  }
+}
+
+void DliLoaderImpl::Impl::ParseAnimations(const TreeNode* tnAnimations, LoadParams& params)
+{
+  auto& definitions = params.output->mAnimationDefinitions;
+  definitions.reserve(definitions.size() + tnAnimations->Size());
+
+  for(TreeNode::ConstIterator iAnim = tnAnimations->CBegin(), iAnimEnd = tnAnimations->CEnd();
+      iAnim != iAnimEnd;
+      ++iAnim)
+  {
+    const TreeNode&     tnAnim = (*iAnim).second;
+    AnimationDefinition animDef;
+    ReadString(tnAnim.GetChild(NAME), animDef.mName);
+
+    auto       iFind     = std::lower_bound(definitions.begin(), definitions.end(), animDef, [](const AnimationDefinition& ad0, const AnimationDefinition& ad1)
+                                  { return ad0.mName < ad1.mName; });
+    const bool overwrite = iFind != definitions.end() && iFind->mName == animDef.mName;
+    if(overwrite)
+    {
+      mOnError(FormatString("Pre-existing animation with name '%s' is being overwritten.", animDef.mName.c_str()));
+    }
+
+    // Duration -- We need something that animated properties' delay / duration can
+    // be expressed as a multiple of; 0 won't work. This is small enough (i.e. shorter
+    // than our frame delay) to not be restrictive WRT replaying. If anything needs
+    // to occur more frequently, then Animations are likely not your solution anyway.
+    animDef.mDuration = AnimationDefinition::MIN_DURATION_SECONDS;
+    if(!ReadFloat(tnAnim.GetChild("duration"), animDef.mDuration))
+    {
+      mOnError(FormatString("Animation '%s' fails to define '%s', defaulting to %f.",
+                            animDef.mName.c_str(),
+                            "duration",
+                            animDef.mDuration));
+    }
+
+    // Get loop count - # of playbacks. Default is once. 0 means repeat indefinitely.
+    animDef.mLoopCount = 1;
+    if(ReadInt(tnAnim.GetChild("loopCount"), animDef.mLoopCount) &&
+       animDef.mLoopCount < 0)
+    {
+      animDef.mLoopCount = 0;
+    }
+
+    std::string endAction;
+    if(ReadString(tnAnim.GetChild("endAction"), endAction))
+    {
+      if("BAKE" == endAction)
+      {
+        animDef.mEndAction = Animation::BAKE;
+      }
+      else if("DISCARD" == endAction)
+      {
+        animDef.mEndAction = Animation::DISCARD;
+      }
+      else if("BAKE_FINAL" == endAction)
+      {
+        animDef.mEndAction = Animation::BAKE_FINAL;
+      }
+    }
+
+    if(ReadString(tnAnim.GetChild("disconnectAction"), endAction))
+    {
+      if("BAKE" == endAction)
+      {
+        animDef.mDisconnectAction = Animation::BAKE;
+      }
+      else if("DISCARD" == endAction)
+      {
+        animDef.mDisconnectAction = Animation::DISCARD;
+      }
+      else if("BAKE_FINAL" == endAction)
+      {
+        animDef.mDisconnectAction = Animation::BAKE_FINAL;
+      }
+    }
+
+    if(const TreeNode* tnProperties = tnAnim.GetChild("properties"))
+    {
+      animDef.mProperties.reserve(tnProperties->Size());
+      for(TreeNode::ConstIterator iProperty = tnProperties->CBegin(), iPropertyEnd = tnProperties->CEnd();
+          iProperty != iPropertyEnd;
+          ++iProperty)
+      {
+        const TreeNode& tnProperty = (*iProperty).second;
+
+        AnimatedProperty animProp;
+        if(!ReadString(tnProperty.GetChild("node"), animProp.mNodeName))
+        {
+          mOnError(FormatString("Animation '%s': Failed to read the 'node' tag.", animDef.mName.c_str()));
+          continue;
+        }
+
+        if(!ReadString(tnProperty.GetChild("property"), animProp.mPropertyName))
+        {
+          mOnError(FormatString("Animation '%s': Failed to read the 'property' tag", animDef.mName.c_str()));
+          continue;
+        }
+
+        // these are the defaults
+        animProp.mTimePeriod.delaySeconds    = 0.f;
+        animProp.mTimePeriod.durationSeconds = animDef.mDuration;
+        if(!ReadTimePeriod(tnProperty.GetChild("timePeriod"), animProp.mTimePeriod))
+        {
+          mOnError(FormatString("Animation '%s': timePeriod missing in Property #%d: defaulting to %f.",
+                                animDef.mName.c_str(),
+                                animDef.mProperties.size(),
+                                animProp.mTimePeriod.durationSeconds));
+        }
+
+        std::string alphaFunctionValue;
+        if(ReadString(tnProperty.GetChild("alphaFunction"), alphaFunctionValue))
+        {
+          animProp.mAlphaFunction = GetAlphaFunction(alphaFunctionValue);
+        }
+
+        if(const TreeNode* tnKeyFramesBin = tnProperty.GetChild("keyFramesBin"))
+        {
+          DALI_ASSERT_ALWAYS(!animProp.mPropertyName.empty() && "Animation must specify a property name");
+
+          std::ifstream binAniFile;
+          std::string   animationFilename;
+          if(ReadString(tnKeyFramesBin->GetChild(URL), animationFilename))
+          {
+            std::string animationFullPath = params.input->mAnimationsPath + animationFilename;
+            binAniFile.open(animationFullPath, std::ios::binary);
+            if(binAniFile.fail())
+            {
+              ExceptionFlinger(ASSERT_LOCATION) << "Failed to open animation data '" << animationFullPath << "'";
+            }
+          }
+
+          int byteOffset = 0;
+          ReadInt(tnKeyFramesBin->GetChild("byteOffset"), byteOffset);
+          DALI_ASSERT_ALWAYS(byteOffset >= 0);
+
+          binAniFile.seekg(byteOffset, std::ios::beg);
+
+          int numKeys = 0;
+          ReadInt(tnKeyFramesBin->GetChild("numKeys"), numKeys);
+          DALI_ASSERT_ALWAYS(numKeys >= 0);
+
+          animProp.mKeyFrames = KeyFrames::New();
+
+          // In binary animation file only is saved the position, rotation, scale and blend shape weight keys.
+          // so, if it is vector3 we assume is position or scale keys, if it is vector4 we assume is rotation,
+          //  otherwise are blend shape weight keys.
+          //  TODO support for binary header with size information
+          Property::Type propType = Property::FLOAT; // assume blend shape weights
+          if(animProp.mPropertyName == "orientation")
+          {
+            propType = Property::VECTOR4;
+          }
+          else if((animProp.mPropertyName == "position") || (animProp.mPropertyName == "scale"))
+          {
+            propType = Property::VECTOR3;
+          }
+
+          // alphafunction is reserved for future implementation
+          //  NOTE: right now we're just using AlphaFunction::LINEAR.
+          unsigned char dummyAlphaFunction;
+
+          float           progress;
+          Property::Value propValue;
+          for(int key = 0; key < numKeys; key++)
+          {
+            binAniFile.read(reinterpret_cast<char*>(&progress), sizeof(float));
+            if(propType == Property::VECTOR3)
+            {
+              Vector3 value;
+              binAniFile.read(reinterpret_cast<char*>(value.AsFloat()), sizeof(float) * 3);
+              propValue = Property::Value(value);
+            }
+            else if(propType == Property::VECTOR4)
+            {
+              Vector4 value;
+              binAniFile.read(reinterpret_cast<char*>(value.AsFloat()), sizeof(float) * 4);
+              propValue = Property::Value(Quaternion(value));
+            }
+            else
+            {
+              float value;
+              binAniFile.read(reinterpret_cast<char*>(&value), sizeof(float));
+              propValue = Property::Value(value);
+            }
+
+            binAniFile.read(reinterpret_cast<char*>(&dummyAlphaFunction), sizeof(unsigned char));
+
+            animProp.mKeyFrames.Add(progress, propValue, AlphaFunction::LINEAR);
+          }
+        }
+        else if(const TreeNode* tnKeyFrames = tnProperty.GetChild("keyFrames"))
+        {
+          DALI_ASSERT_ALWAYS(!animProp.mPropertyName.empty() && "Animation must specify a property name");
+          animProp.mKeyFrames = KeyFrames::New();
+
+          float progress = 0.0f;
+          for(auto i0 = tnKeyFrames->CBegin(), i1 = tnKeyFrames->CEnd(); i1 != i0; ++i0)
+          {
+            const TreeNode::KeyNodePair& kfKeyChild = *i0;
+            bool                         readResult = ReadFloat(kfKeyChild.second.GetChild("progress"), progress);
+            DALI_ASSERT_ALWAYS(readResult && "Key frame entry must have 'progress'");
+
+            const TreeNode* tnValue = kfKeyChild.second.GetChild("value");
+            DALI_ASSERT_ALWAYS(tnValue && "Key frame entry must have 'value'");
+
+            // For the "orientation" property, convert from Vector4 -> Rotation value
+            // This work-around is preferable to a null-pointer exception in the DALi update thread
+            Property::Value propValue(ReadPropertyValue(*tnValue));
+            if(propValue.GetType() == Property::VECTOR4 &&
+               animProp.mPropertyName == "orientation")
+            {
+              Vector4 v;
+              propValue.Get(v);
+              propValue = Property::Value(Quaternion(v.w, v.x, v.y, v.z));
+            }
+
+            AlphaFunction kfAlphaFunction(AlphaFunction::DEFAULT);
+            std::string   alphaFuncStr;
+            if(ReadString(kfKeyChild.second.GetChild("alphaFunction"), alphaFuncStr))
+            {
+              kfAlphaFunction = GetAlphaFunction(alphaFuncStr);
+            }
+
+            animProp.mKeyFrames.Add(progress, propValue, kfAlphaFunction);
+          }
+        }
+        else
+        {
+          const TreeNode* tnValue = tnProperty.GetChild("value");
+          if(tnValue)
+          {
+            animProp.mValue.reset(new AnimatedProperty::Value{ReadPropertyValue(*tnValue)});
+            ReadBool(tnProperty.GetChild("relative"), animProp.mValue->mIsRelative);
+          }
+          else
+          {
+            mOnError(FormatString("Property '%s' fails to define target value.",
+                                  animProp.mPropertyName.c_str()));
+          }
+        }
+
+        animDef.mProperties.push_back(std::move(animProp));
+      }
+    }
+
+    if(overwrite)
+    {
+      *iFind = std::move(animDef);
+    }
+    else
+    {
+      iFind = definitions.insert(iFind, std::move(animDef));
+    }
+
+    if(auto proc = params.input->mAnimationPropertyProcessor) // optional processing
+    {
+      Property::Map map;
+      ParseProperties(tnAnim, map);
+      proc(animDef, std::move(map), mOnError);
+    }
+  }
+}
+
+void DliLoaderImpl::Impl::ParseAnimationGroups(const Toolkit::TreeNode* tnAnimationGroups, LoadParams& params)
+{
+  auto& animGroups = params.output->mAnimationGroupDefinitions;
+
+  int numGroups = 0;
+  for(auto iGroups = tnAnimationGroups->CBegin(), iGroupsEnd = tnAnimationGroups->CEnd();
+      iGroups != iGroupsEnd;
+      ++iGroups, ++numGroups)
+  {
+    const auto& tnGroup = *iGroups;
+    auto        tnName  = tnGroup.second.GetChild(NAME);
+    std::string groupName;
+    if(!tnName || !ReadString(tnName, groupName))
+    {
+      mOnError(FormatString("Failed to get the name for the Animation group %d; ignoring.", numGroups));
+      continue;
+    }
+
+    auto iFind = std::lower_bound(animGroups.begin(), animGroups.end(), groupName, [](const AnimationGroupDefinition& group, const std::string& name)
+                                  { return group.mName < name; });
+    if(iFind != animGroups.end() && iFind->mName == groupName)
+    {
+      mOnError(FormatString("Animation group with name '%s' already exists; new entries will be merged.", groupName.c_str()));
+    }
+    else
+    {
+      iFind = animGroups.insert(iFind, AnimationGroupDefinition{});
+    }
+
+    iFind->mName = groupName;
+
+    auto tnAnims = tnGroup.second.GetChild("animations");
+    if(tnAnims && tnAnims->Size() > 0)
+    {
+      auto& anims = iFind->mAnimations;
+      anims.reserve(anims.size() + tnAnims->Size());
+      for(auto iAnims = tnAnims->CBegin(), iAnimsEnd = tnAnims->CEnd(); iAnims != iAnimsEnd; ++iAnims)
+      {
+        anims.push_back((*iAnims).second.GetString());
+      }
+    }
+  }
+}
+
+void DliLoaderImpl::Impl::GetCameraParameters(std::vector<Dali::Scene3D::Loader::CameraParameters>& cameras) const
+{
+  if(mParser.GetRoot())
+  {
+    if(const TreeNode* jsonCameras = mParser.GetRoot()->GetChild("cameras"))
+    {
+      float dummyFloatArray[4];
+
+      cameras.resize(jsonCameras->Size());
+      auto iCamera = cameras.begin();
+      for(auto i0 = jsonCameras->CBegin(), i1 = jsonCameras->CEnd(); i0 != i1; ++i0)
+      {
+        auto& jsonCamera = (*i0).second;
+
+        ReadFloat(jsonCamera.GetChild("fov"), iCamera->yFovDegree.degree);
+        ReadFloat(jsonCamera.GetChild("near"), iCamera->zNear);
+        ReadFloat(jsonCamera.GetChild("far"), iCamera->zFar);
+        if(ReadVector(jsonCamera.GetChild("orthographic"), dummyFloatArray, 4u))
+        {
+          iCamera->isPerspective = false;
+
+          iCamera->orthographicSize = dummyFloatArray[2] * 0.5f;
+          iCamera->aspectRatio      = dummyFloatArray[1] / dummyFloatArray[2];
+        }
+
+        if(auto jsonMatrix = jsonCamera.GetChild("matrix"))
+        {
+          ReadVector(jsonMatrix, iCamera->matrix.AsFloat(), 16u);
+        }
+
+        ++iCamera;
+      }
+    }
+  }
+}
+
+void DliLoaderImpl::Impl::GetLightParameters(std::vector<Dali::Scene3D::Loader::LightParameters>& lights) const
+{
+  if(mParser.GetRoot())
+  {
+    if(const TreeNode* jsonLights = mParser.GetRoot()->GetChild("lights"))
+    {
+      lights.resize(jsonLights->Size());
+      auto iLight = lights.begin();
+      for(auto i0 = jsonLights->CBegin(), i1 = jsonLights->CEnd(); i0 != i1; ++i0)
+      {
+        auto& jsonLight = (*i0).second;
+        if(!ReadVector(jsonLight.GetChild("matrix"), iLight->transform.AsFloat(), 16))
+        {
+          mOnError(
+            FormatString("Failed to parse light %d - \"matrix\" child with 16 floats expected.\n",
+                         std::distance(jsonLights->CBegin(), i0)));
+          continue;
+        }
+
+        int shadowMapSize = 0;
+        if(ReadInt(jsonLight.GetChild(SHADOW_MAP_SIZE), shadowMapSize) && shadowMapSize < 0)
+        {
+          mOnError(
+            FormatString("Failed to parse light %d - %s has an invalid value.",
+                         std::distance(jsonLights->CBegin(), i0),
+                         SHADOW_MAP_SIZE));
+          continue;
+        }
+        iLight->shadowMapSize = shadowMapSize;
+
+        float orthoSize = 0.f;
+        if(ReadFloat(jsonLight.GetChild(ORTHOGRAPHIC_SIZE), orthoSize) &&
+           (orthoSize < .0f || std::isnan(orthoSize) || std::isinf(orthoSize)))
+        {
+          mOnError(
+            FormatString("Failed to parse light %d - %s has an invalid value.",
+                         std::distance(jsonLights->CBegin(), i0),
+                         ORTHOGRAPHIC_SIZE));
+          continue;
+        }
+        iLight->orthographicSize = orthoSize;
+
+        if((iLight->shadowMapSize > 0) != (iLight->orthographicSize > .0f))
+        {
+          mOnError(FormatString(
+            "Light %d: Both shadow map size and orthographic size must be set for shadows to work.",
+            std::distance(jsonLights->CBegin(), i0)));
+        }
+
+        if(!ReadVector(jsonLight.GetChild("color"), iLight->color.AsFloat(), 3)) // color is optional
+        {
+          iLight->color = Vector3::ONE; // default to white
+        }
+
+        if(!ReadFloat(jsonLight.GetChild("intensity"), iLight->intensity)) // intensity is optional
+        {
+          iLight->intensity = 1.0f; // default to 1.0
+        }
+
+        if(!ReadFloat(jsonLight.GetChild("shadowIntensity"), iLight->shadowIntensity)) // intensity is optional
+        {
+          iLight->shadowIntensity = 1.0f; // default to 1.0
+        }
+
+        ++iLight;
+      }
+    }
+  }
+}
+
+} // namespace Internal
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
diff --git a/dali-scene3d/internal/loader/dli-loader-impl.h b/dali-scene3d/internal/loader/dli-loader-impl.h
new file mode 100644 (file)
index 0000000..a68432c
--- /dev/null
@@ -0,0 +1,84 @@
+#ifndef DALI_SCENE3D_LOADER_DLI_LOADER_IMPL_H
+#define DALI_SCENE3D_LOADER_DLI_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/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/node-definition.h>
+#include <dali-scene3d/public-api/loader/string-callback.h>
+
+// EXTERNAL INCLUDES
+#include "dali/public-api/common/vector-wrapper.h"
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Loader
+{
+namespace Internal
+{
+typedef std::pair<std::string, std::string> Metadata;
+
+// Forward declarations
+struct LoadResult;
+
+class DliLoaderImpl : public ModelLoaderImpl
+{
+public:
+  struct LoadParams
+  {
+    Dali::Scene3D::Loader::DliInputParameter* input;
+    Dali::Scene3D::Loader::LoadResult*        output;
+  };
+
+  DliLoaderImpl();
+  ~DliLoaderImpl();
+
+  /**
+   * @brief Sets the callback that messages from non-fatal errors get posted to.
+   *  Uses DefaultErrorCallback by default.
+   */
+  void SetErrorCallback(StringCallback onError);
+
+  /**
+   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl()
+   */
+  bool LoadModel(const std::string& uri, Dali::Scene3D::Loader::LoadResult& result) override;
+
+  /**
+   * @return The error string describing how the parse has failed, if any.
+   */
+  std::string GetParseError() const;
+
+private:
+  struct Impl;
+  const std::unique_ptr<Impl> mImpl;
+};
+
+} // namespace Internal
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
+
+#endif // DALI_SCENE3D_LOADER_DLI_LOADER_IMPL_H
diff --git a/dali-scene3d/internal/loader/gltf2-loader-impl.cpp b/dali-scene3d/internal/loader/gltf2-loader-impl.cpp
new file mode 100644 (file)
index 0000000..324d5ad
--- /dev/null
@@ -0,0 +1,1399 @@
+/*
+ * 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.
+ *
+ */
+
+// FILE HEADER
+#include <dali-scene3d/internal/loader/gltf2-loader-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/integration-api/debug.h>
+#include <dali/public-api/images/image-operations.h>
+#include <dali/public-api/math/quaternion.h>
+#include <memory>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/internal/loader/gltf2-asset.h>
+#include <dali-scene3d/public-api/loader/load-result.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-definition-factory.h>
+#include <dali-scene3d/public-api/loader/utils.h>
+
+namespace gt = gltf2;
+namespace js = json;
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Loader
+{
+namespace Internal
+{
+namespace
+{
+
+const std::string POSITION_PROPERTY("position");
+const std::string ORIENTATION_PROPERTY("orientation");
+const std::string SCALE_PROPERTY("scale");
+const std::string BLEND_SHAPE_WEIGHTS_UNIFORM("uBlendShapeWeight");
+const std::string MRENDERER_MODEL_IDENTIFICATION("M-Renderer");
+const std::string ROOT_NODE_NAME("RootNode");
+const Vector3     SCALE_TO_ADJUST(100.0f, 100.0f, 100.0f);
+
+const Geometry::Type GLTF2_TO_DALI_PRIMITIVES[]{
+  Geometry::POINTS,
+  Geometry::LINES,
+  Geometry::LINE_LOOP,
+  Geometry::LINE_STRIP,
+  Geometry::TRIANGLES,
+  Geometry::TRIANGLE_STRIP,
+  Geometry::TRIANGLE_FAN}; //...because Dali swaps the last two.
+
+struct AttributeMapping
+{
+  gt::Attribute::Type      mType;
+  MeshDefinition::Accessor MeshDefinition::*mAccessor;
+  uint16_t                                  mElementSizeRequired;
+} ATTRIBUTE_MAPPINGS[]{
+  {gt::Attribute::NORMAL, &MeshDefinition::mNormals, sizeof(Vector3)},
+  {gt::Attribute::TANGENT, &MeshDefinition::mTangents, sizeof(Vector3)},
+  {gt::Attribute::TEXCOORD_0, &MeshDefinition::mTexCoords, sizeof(Vector2)},
+  {gt::Attribute::COLOR_0, &MeshDefinition::mColors, sizeof(Vector4)},
+  {gt::Attribute::JOINTS_0, &MeshDefinition::mJoints0, sizeof(Vector4)},
+  {gt::Attribute::WEIGHTS_0, &MeshDefinition::mWeights0, sizeof(Vector4)},
+};
+
+std::vector<gt::Animation> ReadAnimationArray(const json_value_s& j)
+{
+  auto results = js::Read::Array<gt::Animation, js::ObjectReader<gt::Animation>::Read>(j);
+
+  for(auto& animation : results)
+  {
+    for(auto& channel : animation.mChannels)
+    {
+      channel.mSampler.UpdateVector(animation.mSamplers);
+    }
+  }
+
+  return results;
+}
+
+void ApplyAccessorMinMax(const gt::Accessor& acc, float* values)
+{
+  DALI_ASSERT_ALWAYS(acc.mMax.empty() || gt::AccessorType::ElementCount(acc.mType) == acc.mMax.size());
+  DALI_ASSERT_ALWAYS(acc.mMin.empty() || gt::AccessorType::ElementCount(acc.mType) == acc.mMin.size());
+  MeshDefinition::Blob::ApplyMinMax(acc.mMin, acc.mMax, acc.mCount, values);
+}
+
+const auto BUFFER_READER = std::move(js::Reader<gt::Buffer>()
+                                       .Register(*js::MakeProperty("byteLength", js::Read::Number<uint32_t>, &gt::Buffer::mByteLength))
+                                       .Register(*js::MakeProperty("uri", js::Read::StringView, &gt::Buffer::mUri)));
+
+const auto BUFFER_VIEW_READER = std::move(js::Reader<gt::BufferView>()
+                                            .Register(*js::MakeProperty("buffer", gt::RefReader<gt::Document>::Read<gt::Buffer, &gt::Document::mBuffers>, &gt::BufferView::mBuffer))
+                                            .Register(*js::MakeProperty("byteOffset", js::Read::Number<uint32_t>, &gt::BufferView::mByteOffset))
+                                            .Register(*js::MakeProperty("byteLength", js::Read::Number<uint32_t>, &gt::BufferView::mByteLength))
+                                            .Register(*js::MakeProperty("byteStride", js::Read::Number<uint32_t>, &gt::BufferView::mByteStride))
+                                            .Register(*js::MakeProperty("target", js::Read::Number<uint32_t>, &gt::BufferView::mTarget)));
+
+const auto BUFFER_VIEW_CLIENT_READER = std::move(js::Reader<gt::BufferViewClient>()
+                                                   .Register(*js::MakeProperty("bufferView", gt::RefReader<gt::Document>::Read<gt::BufferView, &gt::Document::mBufferViews>, &gt::BufferViewClient::mBufferView))
+                                                   .Register(*js::MakeProperty("byteOffset", js::Read::Number<uint32_t>, &gt::BufferViewClient::mByteOffset)));
+
+const auto COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER = std::move(js::Reader<gt::ComponentTypedBufferViewClient>()
+                                                                   .Register(*new js::Property<gt::ComponentTypedBufferViewClient, gt::Ref<gt::BufferView>>("bufferView", gt::RefReader<gt::Document>::Read<gt::BufferView, &gt::Document::mBufferViews>, &gt::ComponentTypedBufferViewClient::mBufferView))
+                                                                   .Register(*new js::Property<gt::ComponentTypedBufferViewClient, uint32_t>("byteOffset", js::Read::Number<uint32_t>, &gt::ComponentTypedBufferViewClient::mByteOffset))
+                                                                   .Register(*js::MakeProperty("componentType", js::Read::Enum<gt::Component::Type>, &gt::ComponentTypedBufferViewClient::mComponentType)));
+
+const auto ACCESSOR_SPARSE_READER = std::move(js::Reader<gt::Accessor::Sparse>()
+                                                .Register(*js::MakeProperty("count", js::Read::Number<uint32_t>, &gt::Accessor::Sparse::mCount))
+                                                .Register(*js::MakeProperty("indices", js::ObjectReader<gt::ComponentTypedBufferViewClient>::Read, &gt::Accessor::Sparse::mIndices))
+                                                .Register(*js::MakeProperty("values", js::ObjectReader<gt::BufferViewClient>::Read, &gt::Accessor::Sparse::mValues)));
+
+const auto ACCESSOR_READER = std::move(js::Reader<gt::Accessor>()
+                                         .Register(*new js::Property<gt::Accessor, gt::Ref<gt::BufferView>>("bufferView",
+                                                                                                            gt::RefReader<gt::Document>::Read<gt::BufferView, &gt::Document::mBufferViews>,
+                                                                                                            &gt::Accessor::mBufferView))
+                                         .Register(*new js::Property<gt::Accessor, uint32_t>("byteOffset",
+                                                                                             js::Read::Number<uint32_t>,
+                                                                                             &gt::Accessor::mByteOffset))
+                                         .Register(*new js::Property<gt::Accessor, gt::Component::Type>("componentType",
+                                                                                                        js::Read::Enum<gt::Component::Type>,
+                                                                                                        &gt::Accessor::mComponentType))
+                                         .Register(*new js::Property<gt::Accessor, std::string_view>("name", js::Read::StringView, &gt::Accessor::mName))
+                                         .Register(*js::MakeProperty("count", js::Read::Number<uint32_t>, &gt::Accessor::mCount))
+                                         .Register(*js::MakeProperty("normalized", js::Read::Boolean, &gt::Accessor::mNormalized))
+                                         .Register(*js::MakeProperty("type", gt::ReadStringEnum<gt::AccessorType>, &gt::Accessor::mType))
+                                         .Register(*js::MakeProperty("min", js::Read::Array<float, js::Read::Number>, &gt::Accessor::mMin))
+                                         .Register(*js::MakeProperty("max", js::Read::Array<float, js::Read::Number>, &gt::Accessor::mMax))
+                                         .Register(*new js::Property<gt::Accessor, gt::Accessor::Sparse>("sparse", js::ObjectReader<gt::Accessor::Sparse>::Read, &gt::Accessor::SetSparse)));
+
+const auto IMAGE_READER = std::move(js::Reader<gt::Image>()
+                                      .Register(*new js::Property<gt::Image, std::string_view>("name", js::Read::StringView, &gt::Material::mName))
+                                      .Register(*js::MakeProperty("uri", js::Read::StringView, &gt::Image::mUri))
+                                      .Register(*js::MakeProperty("mimeType", js::Read::StringView, &gt::Image::mMimeType))
+                                      .Register(*js::MakeProperty("bufferView", gt::RefReader<gt::Document>::Read<gt::BufferView, &gt::Document::mBufferViews>, &gt::Image::mBufferView)));
+
+const auto SAMPLER_READER = std::move(js::Reader<gt::Sampler>()
+                                        .Register(*js::MakeProperty("minFilter", js::Read::Enum<gt::Filter::Type>, &gt::Sampler::mMinFilter))
+                                        .Register(*js::MakeProperty("magFilter", js::Read::Enum<gt::Filter::Type>, &gt::Sampler::mMagFilter))
+                                        .Register(*js::MakeProperty("wrapS", js::Read::Enum<gt::Wrap::Type>, &gt::Sampler::mWrapS))
+                                        .Register(*js::MakeProperty("wrapT", js::Read::Enum<gt::Wrap::Type>, &gt::Sampler::mWrapT)));
+
+const auto TEXURE_READER = std::move(js::Reader<gt::Texture>()
+                                       .Register(*js::MakeProperty("source", gt::RefReader<gt::Document>::Read<gt::Image, &gt::Document::mImages>, &gt::Texture::mSource))
+                                       .Register(*js::MakeProperty("sampler", gt::RefReader<gt::Document>::Read<gt::Sampler, &gt::Document::mSamplers>, &gt::Texture::mSampler)));
+
+const auto TEXURE_INFO_READER = std::move(js::Reader<gt::TextureInfo>()
+                                            .Register(*js::MakeProperty("index", gt::RefReader<gt::Document>::Read<gt::Texture, &gt::Document::mTextures>, &gt::TextureInfo::mTexture))
+                                            .Register(*js::MakeProperty("texCoord", js::Read::Number<uint32_t>, &gt::TextureInfo::mTexCoord))
+                                            .Register(*js::MakeProperty("scale", js::Read::Number<float>, &gt::TextureInfo::mScale))
+                                            .Register(*js::MakeProperty("strength", js::Read::Number<float>, &gt::TextureInfo::mStrength)));
+
+const auto MATERIAL_PBR_READER = std::move(js::Reader<gt::Material::Pbr>()
+                                             .Register(*js::MakeProperty("baseColorFactor", gt::ReadDaliVector<Vector4>, &gt::Material::Pbr::mBaseColorFactor))
+                                             .Register(*js::MakeProperty("baseColorTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::Material::Pbr::mBaseColorTexture))
+                                             .Register(*js::MakeProperty("metallicFactor", js::Read::Number<float>, &gt::Material::Pbr::mMetallicFactor))
+                                             .Register(*js::MakeProperty("roughnessFactor", js::Read::Number<float>, &gt::Material::Pbr::mRoughnessFactor))
+                                             .Register(*js::MakeProperty("metallicRoughnessTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::Material::Pbr::mMetallicRoughnessTexture)));
+
+const auto MATERIAL_SPECULAR_READER = std::move(js::Reader<gt::MaterialSpecular>()
+                                                  .Register(*js::MakeProperty("specularFactor", js::Read::Number<float>, &gt::MaterialSpecular::mSpecularFactor))
+                                                  .Register(*js::MakeProperty("specularTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::MaterialSpecular::mSpecularTexture))
+                                                  .Register(*js::MakeProperty("specularColorFactor", gt::ReadDaliVector<Vector3>, &gt::MaterialSpecular::mSpecularColorFactor))
+                                                  .Register(*js::MakeProperty("specularColorTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::MaterialSpecular::mSpecularColorTexture)));
+
+const auto MATERIAL_IOR_READER = std::move(js::Reader<gt::MaterialIor>()
+                                             .Register(*js::MakeProperty("ior", js::Read::Number<float>, &gt::MaterialIor::mIor)));
+
+const auto MATERIAL_EXTENSION_READER = std::move(js::Reader<gt::MaterialExtensions>()
+                                                   .Register(*js::MakeProperty("KHR_materials_ior", js::ObjectReader<gt::MaterialIor>::Read, &gt::MaterialExtensions::mMaterialIor))
+                                                   .Register(*js::MakeProperty("KHR_materials_specular", js::ObjectReader<gt::MaterialSpecular>::Read, &gt::MaterialExtensions::mMaterialSpecular)));
+
+const auto MATERIAL_READER = std::move(js::Reader<gt::Material>()
+                                         .Register(*new js::Property<gt::Material, std::string_view>("name", js::Read::StringView, &gt::Material::mName))
+                                         .Register(*js::MakeProperty("pbrMetallicRoughness", js::ObjectReader<gt::Material::Pbr>::Read, &gt::Material::mPbrMetallicRoughness))
+                                         .Register(*js::MakeProperty("normalTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::Material::mNormalTexture))
+                                         .Register(*js::MakeProperty("occlusionTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::Material::mOcclusionTexture))
+                                         .Register(*js::MakeProperty("emissiveTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::Material::mEmissiveTexture))
+                                         .Register(*js::MakeProperty("emissiveFactor", gt::ReadDaliVector<Vector3>, &gt::Material::mEmissiveFactor))
+                                         .Register(*js::MakeProperty("alphaMode", gt::ReadStringEnum<gt::AlphaMode>, &gt::Material::mAlphaMode))
+                                         .Register(*js::MakeProperty("alphaCutoff", js::Read::Number<float>, &gt::Material::mAlphaCutoff))
+                                         .Register(*js::MakeProperty("doubleSided", js::Read::Boolean, &gt::Material::mDoubleSided))
+                                         .Register(*js::MakeProperty("extensions", js::ObjectReader<gt::MaterialExtensions>::Read, &gt::Material::mMaterialExtensions)));
+
+std::map<gt::Attribute::Type, gt::Ref<gt::Accessor>> ReadMeshPrimitiveAttributes(const json_value_s& j)
+{
+  auto&                                                jo = js::Cast<json_object_s>(j);
+  std::map<gt::Attribute::Type, gt::Ref<gt::Accessor>> result;
+
+  auto i = jo.start;
+  while(i)
+  {
+    auto jstr                                                        = *i->name;
+    result[gt::Attribute::FromString(jstr.string, jstr.string_size)] = gt::RefReader<gt::Document>::Read<gt::Accessor, &gt::Document::mAccessors>(*i->value);
+    i                                                                = i->next;
+  }
+  return result;
+}
+
+std::vector<std::map<gt::Attribute::Type, gt::Ref<gt::Accessor>>> ReadMeshPrimitiveTargets(const json_value_s& j)
+{
+  auto&                                                             jo = js::Cast<json_array_s>(j);
+  std::vector<std::map<gt::Attribute::Type, gt::Ref<gt::Accessor>>> result;
+
+  result.reserve(jo.length);
+
+  auto i = jo.start;
+  while(i)
+  {
+    result.push_back(std::move(ReadMeshPrimitiveAttributes(*i->value)));
+    i = i->next;
+  }
+
+  return result;
+}
+
+const auto MESH_PRIMITIVE_READER = std::move(js::Reader<gt::Mesh::Primitive>()
+                                               .Register(*js::MakeProperty("attributes", ReadMeshPrimitiveAttributes, &gt::Mesh::Primitive::mAttributes))
+                                               .Register(*js::MakeProperty("indices", gt::RefReader<gt::Document>::Read<gt::Accessor, &gt::Document::mAccessors>, &gt::Mesh::Primitive::mIndices))
+                                               .Register(*js::MakeProperty("material", gt::RefReader<gt::Document>::Read<gt::Material, &gt::Document::mMaterials>, &gt::Mesh::Primitive::mMaterial))
+                                               .Register(*js::MakeProperty("mode", js::Read::Enum<gt::Mesh::Primitive::Mode>, &gt::Mesh::Primitive::mMode))
+                                               .Register(*js::MakeProperty("targets", ReadMeshPrimitiveTargets, &gt::Mesh::Primitive::mTargets)));
+
+const auto MESH_READER = std::move(js::Reader<gt::Mesh>()
+                                     .Register(*new js::Property<gt::Mesh, std::string_view>("name", js::Read::StringView, &gt::Mesh::mName))
+                                     .Register(*js::MakeProperty("primitives",
+                                                                 js::Read::Array<gt::Mesh::Primitive, js::ObjectReader<gt::Mesh::Primitive>::Read>,
+                                                                 &gt::Mesh::mPrimitives))
+                                     .Register(*js::MakeProperty("weights", js::Read::Array<float, js::Read::Number>, &gt::Mesh::mWeights)));
+
+const auto SKIN_READER = std::move(js::Reader<gt::Skin>()
+                                     .Register(*new js::Property<gt::Skin, std::string_view>("name", js::Read::StringView, &gt::Skin::mName))
+                                     .Register(*js::MakeProperty("inverseBindMatrices",
+                                                                 gt::RefReader<gt::Document>::Read<gt::Accessor, &gt::Document::mAccessors>,
+                                                                 &gt::Skin::mInverseBindMatrices))
+                                     .Register(*js::MakeProperty("skeleton",
+                                                                 gt::RefReader<gt::Document>::Read<gt::Node, &gt::Document::mNodes>,
+                                                                 &gt::Skin::mSkeleton))
+                                     .Register(*js::MakeProperty("joints",
+                                                                 js::Read::Array<gt::Ref<gt::Node>, gt::RefReader<gt::Document>::Read<gt::Node, &gt::Document::mNodes>>,
+                                                                 &gt::Skin::mJoints)));
+
+const auto CAMERA_PERSPECTIVE_READER = std::move(js::Reader<gt::Camera::Perspective>()
+                                                   .Register(*js::MakeProperty("aspectRatio", js::Read::Number<float>, &gt::Camera::Perspective::mAspectRatio))
+                                                   .Register(*js::MakeProperty("yfov", js::Read::Number<float>, &gt::Camera::Perspective::mYFov))
+                                                   .Register(*js::MakeProperty("zfar", js::Read::Number<float>, &gt::Camera::Perspective::mZFar))
+                                                   .Register(*js::MakeProperty("znear", js::Read::Number<float>, &gt::Camera::Perspective::mZNear))); // TODO: infinite perspective projection, where znear is omitted
+
+const auto CAMERA_ORTHOGRAPHIC_READER = std::move(js::Reader<gt::Camera::Orthographic>()
+                                                    .Register(*js::MakeProperty("xmag", js::Read::Number<float>, &gt::Camera::Orthographic::mXMag))
+                                                    .Register(*js::MakeProperty("ymag", js::Read::Number<float>, &gt::Camera::Orthographic::mYMag))
+                                                    .Register(*js::MakeProperty("zfar", js::Read::Number<float>, &gt::Camera::Orthographic::mZFar))
+                                                    .Register(*js::MakeProperty("znear", js::Read::Number<float>, &gt::Camera::Orthographic::mZNear)));
+
+const auto CAMERA_READER = std::move(js::Reader<gt::Camera>()
+                                       .Register(*new js::Property<gt::Camera, std::string_view>("name", js::Read::StringView, &gt::Camera::mName))
+                                       .Register(*js::MakeProperty("type", js::Read::StringView, &gt::Camera::mType))
+                                       .Register(*js::MakeProperty("perspective", js::ObjectReader<gt::Camera::Perspective>::Read, &gt::Camera::mPerspective))
+                                       .Register(*js::MakeProperty("orthographic", js::ObjectReader<gt::Camera::Orthographic>::Read, &gt::Camera::mOrthographic)));
+
+const auto NODE_READER = std::move(js::Reader<gt::Node>()
+                                     .Register(*new js::Property<gt::Node, std::string_view>("name", js::Read::StringView, &gt::Node::mName))
+                                     .Register(*js::MakeProperty("translation", gt::ReadDaliVector<Vector3>, &gt::Node::mTranslation))
+                                     .Register(*js::MakeProperty("rotation", gt::ReadQuaternion, &gt::Node::mRotation))
+                                     .Register(*js::MakeProperty("scale", gt::ReadDaliVector<Vector3>, &gt::Node::mScale))
+                                     .Register(*new js::Property<gt::Node, Matrix>("matrix", gt::ReadDaliVector<Matrix>, &gt::Node::SetMatrix))
+                                     .Register(*js::MakeProperty("camera", gt::RefReader<gt::Document>::Read<gt::Camera, &gt::Document::mCameras>, &gt::Node::mCamera))
+                                     .Register(*js::MakeProperty("children", js::Read::Array<gt::Ref<gt::Node>, gt::RefReader<gt::Document>::Read<gt::Node, &gt::Document::mNodes>>, &gt::Node::mChildren))
+                                     .Register(*js::MakeProperty("mesh", gt::RefReader<gt::Document>::Read<gt::Mesh, &gt::Document::mMeshes>, &gt::Node::mMesh))
+                                     .Register(*js::MakeProperty("skin", gt::RefReader<gt::Document>::Read<gt::Skin, &gt::Document::mSkins>, &gt::Node::mSkin)));
+
+const auto ANIMATION_SAMPLER_READER = std::move(js::Reader<gt::Animation::Sampler>()
+                                                  .Register(*js::MakeProperty("input", gt::RefReader<gt::Document>::Read<gt::Accessor, &gt::Document::mAccessors>, &gt::Animation::Sampler::mInput))
+                                                  .Register(*js::MakeProperty("output", gt::RefReader<gt::Document>::Read<gt::Accessor, &gt::Document::mAccessors>, &gt::Animation::Sampler::mOutput))
+                                                  .Register(*js::MakeProperty("interpolation", gt::ReadStringEnum<gt::Animation::Sampler::Interpolation>, &gt::Animation::Sampler::mInterpolation)));
+
+const auto ANIMATION_TARGET_READER = std::move(js::Reader<gt::Animation::Channel::Target>()
+                                                 .Register(*js::MakeProperty("node", gt::RefReader<gt::Document>::Read<gt::Node, &gt::Document::mNodes>, &gt::Animation::Channel::Target::mNode))
+                                                 .Register(*js::MakeProperty("path", gt::ReadStringEnum<gt::Animation::Channel::Target>, &gt::Animation::Channel::Target::mPath)));
+
+const auto ANIMATION_CHANNEL_READER = std::move(js::Reader<gt::Animation::Channel>()
+                                                  .Register(*js::MakeProperty("target", js::ObjectReader<gt::Animation::Channel::Target>::Read, &gt::Animation::Channel::mTarget))
+                                                  .Register(*js::MakeProperty("sampler", gt::RefReader<gt::Animation>::Read<gt::Animation::Sampler, &gt::Animation::mSamplers>, &gt::Animation::Channel::mSampler)));
+
+const auto ANIMATION_READER = std::move(js::Reader<gt::Animation>()
+                                          .Register(*new js::Property<gt::Animation, std::string_view>("name", js::Read::StringView, &gt::Animation::mName))
+                                          .Register(*js::MakeProperty("samplers",
+                                                                      js::Read::Array<gt::Animation::Sampler, js::ObjectReader<gt::Animation::Sampler>::Read>,
+                                                                      &gt::Animation::mSamplers))
+                                          .Register(*js::MakeProperty("channels",
+                                                                      js::Read::Array<gt::Animation::Channel, js::ObjectReader<gt::Animation::Channel>::Read>,
+                                                                      &gt::Animation::mChannels)));
+
+const auto SCENE_READER = std::move(js::Reader<gt::Scene>()
+                                      .Register(*new js::Property<gt::Scene, std::string_view>("name", js::Read::StringView, &gt::Scene::mName))
+                                      .Register(*js::MakeProperty("nodes",
+                                                                  js::Read::Array<gt::Ref<gt::Node>, gt::RefReader<gt::Document>::Read<gt::Node, &gt::Document::mNodes>>,
+                                                                  &gt::Scene::mNodes)));
+
+const auto DOCUMENT_READER = std::move(js::Reader<gt::Document>()
+                                         .Register(*js::MakeProperty("buffers",
+                                                                     js::Read::Array<gt::Buffer, js::ObjectReader<gt::Buffer>::Read>,
+                                                                     &gt::Document::mBuffers))
+                                         .Register(*js::MakeProperty("bufferViews",
+                                                                     js::Read::Array<gt::BufferView, js::ObjectReader<gt::BufferView>::Read>,
+                                                                     &gt::Document::mBufferViews))
+                                         .Register(*js::MakeProperty("accessors",
+                                                                     js::Read::Array<gt::Accessor, js::ObjectReader<gt::Accessor>::Read>,
+                                                                     &gt::Document::mAccessors))
+                                         .Register(*js::MakeProperty("images",
+                                                                     js::Read::Array<gt::Image, js::ObjectReader<gt::Image>::Read>,
+                                                                     &gt::Document::mImages))
+                                         .Register(*js::MakeProperty("samplers",
+                                                                     js::Read::Array<gt::Sampler, js::ObjectReader<gt::Sampler>::Read>,
+                                                                     &gt::Document::mSamplers))
+                                         .Register(*js::MakeProperty("textures",
+                                                                     js::Read::Array<gt::Texture, js::ObjectReader<gt::Texture>::Read>,
+                                                                     &gt::Document::mTextures))
+                                         .Register(*js::MakeProperty("materials",
+                                                                     js::Read::Array<gt::Material, js::ObjectReader<gt::Material>::Read>,
+                                                                     &gt::Document::mMaterials))
+                                         .Register(*js::MakeProperty("meshes",
+                                                                     js::Read::Array<gt::Mesh, js::ObjectReader<gt::Mesh>::Read>,
+                                                                     &gt::Document::mMeshes))
+                                         .Register(*js::MakeProperty("skins",
+                                                                     js::Read::Array<gt::Skin, js::ObjectReader<gt::Skin>::Read>,
+                                                                     &gt::Document::mSkins))
+                                         .Register(*js::MakeProperty("cameras",
+                                                                     js::Read::Array<gt::Camera, js::ObjectReader<gt::Camera>::Read>,
+                                                                     &gt::Document::mCameras))
+                                         .Register(*js::MakeProperty("nodes",
+                                                                     js::Read::Array<gt::Node, js::ObjectReader<gt::Node>::Read>,
+                                                                     &gt::Document::mNodes))
+                                         .Register(*js::MakeProperty("animations",
+                                                                     ReadAnimationArray,
+                                                                     &gt::Document::mAnimations))
+                                         .Register(*js::MakeProperty("scenes",
+                                                                     js::Read::Array<gt::Scene, js::ObjectReader<gt::Scene>::Read>,
+                                                                     &gt::Document::mScenes))
+                                         .Register(*js::MakeProperty("scene", gt::RefReader<gt::Document>::Read<gt::Scene, &gt::Document::mScenes>, &gt::Document::mScene)));
+
+struct NodeMapping
+{
+  Index gltfIdx;
+  Index runtimeIdx;
+};
+
+bool operator<(const NodeMapping& mapping, Index gltfIdx)
+{
+  return mapping.gltfIdx < gltfIdx;
+}
+
+class NodeIndexMapper
+{
+public:
+  NodeIndexMapper()                                  = default;
+  NodeIndexMapper(const NodeIndexMapper&)            = delete;
+  NodeIndexMapper& operator=(const NodeIndexMapper&) = delete;
+
+  ///@brief Registers a mapping of the @a gltfIdx of a node to its @a runtimeIdx .
+  ///@note If the indices are the same, the registration is omitted, in order to
+  /// save growing a vector.
+  void RegisterMapping(Index gltfIdx, Index runtimeIdx)
+  {
+    if(gltfIdx != runtimeIdx)
+    {
+      auto iInsert = std::lower_bound(mNodes.begin(), mNodes.end(), gltfIdx);
+      DALI_ASSERT_DEBUG(iInsert == mNodes.end() || iInsert->gltfIdx != gltfIdx);
+      mNodes.insert(iInsert, NodeMapping{gltfIdx, runtimeIdx});
+    }
+  }
+
+  ///@brief Retrieves the runtime index of a Node, mapped to the given @a gltfIdx.
+  Index GetRuntimeId(Index gltfIdx) const
+  {
+    auto iFind = std::lower_bound(mNodes.begin(), mNodes.end(), gltfIdx); // using custom operator<
+    return (iFind != mNodes.end() && iFind->gltfIdx == gltfIdx) ? iFind->runtimeIdx : gltfIdx;
+  }
+
+private:
+  std::vector<NodeMapping> mNodes;
+};
+
+struct ConversionContext
+{
+  LoadResult& mOutput;
+
+  std::string mPath;
+  Index       mDefaultMaterial;
+
+  std::vector<Index> mMeshIds;
+  NodeIndexMapper    mNodeIndices;
+};
+
+void ConvertBuffer(const gt::Buffer& buffer, decltype(ResourceBundle::mBuffers)& outBuffers, const std::string& resourcePath)
+{
+  BufferDefinition bufferDefinition;
+
+  bufferDefinition.mResourcePath = resourcePath;
+  bufferDefinition.mUri          = buffer.mUri;
+  bufferDefinition.mByteLength   = buffer.mByteLength;
+
+  outBuffers.emplace_back(std::move(bufferDefinition));
+}
+
+void ConvertBuffers(const gt::Document& doc, ConversionContext& context)
+{
+  auto& outBuffers = context.mOutput.mResources.mBuffers;
+  outBuffers.reserve(doc.mBuffers.size());
+
+  for(auto& buffer : doc.mBuffers)
+  {
+    ConvertBuffer(buffer, outBuffers, context.mPath);
+  }
+}
+
+SamplerFlags::Type ConvertWrapMode(gt::Wrap::Type wrapMode)
+{
+  switch(wrapMode)
+  {
+    case gt::Wrap::REPEAT:
+      return SamplerFlags::WRAP_REPEAT;
+    case gt::Wrap::CLAMP_TO_EDGE:
+      return SamplerFlags::WRAP_CLAMP;
+    case gt::Wrap::MIRRORED_REPEAT:
+      return SamplerFlags::WRAP_MIRROR;
+    default:
+      throw std::runtime_error("Invalid wrap type.");
+  }
+}
+
+SamplerFlags::Type ConvertSampler(const gt::Ref<gt::Sampler>& sampler)
+{
+  if(sampler)
+  {
+    return ((sampler->mMinFilter < gt::Filter::NEAREST_MIPMAP_NEAREST) ? (sampler->mMinFilter - gt::Filter::NEAREST) : ((sampler->mMinFilter - gt::Filter::NEAREST_MIPMAP_NEAREST) + 2)) |
+           ((sampler->mMagFilter - gt::Filter::NEAREST) << SamplerFlags::FILTER_MAG_SHIFT) |
+           (ConvertWrapMode(sampler->mWrapS) << SamplerFlags::WRAP_S_SHIFT) |
+           (ConvertWrapMode(sampler->mWrapT) << SamplerFlags::WRAP_T_SHIFT);
+  }
+  else
+  {
+    // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#texturesampler
+    // "The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering should be used."
+    // "What is an auto filtering", I hear you ask. Since there's nothing else to determine mipmapping from - including glTF image
+    // properties, if not in some extension -, we will simply assume linear filtering.
+    return SamplerFlags::FILTER_LINEAR | (SamplerFlags::FILTER_LINEAR << SamplerFlags::FILTER_MAG_SHIFT) |
+           (SamplerFlags::WRAP_REPEAT << SamplerFlags::WRAP_S_SHIFT) | (SamplerFlags::WRAP_REPEAT << SamplerFlags::WRAP_T_SHIFT);
+  }
+}
+
+TextureDefinition ConvertTextureInfo(const gt::TextureInfo& mm, ConversionContext& context, const ImageMetadata& metaData = ImageMetadata())
+{
+  TextureDefinition textureDefinition;
+  std::string       uri = std::string(mm.mTexture->mSource->mUri);
+  if(uri.empty())
+  {
+    uint32_t bufferIndex = mm.mTexture->mSource->mBufferView->mBuffer.GetIndex();
+    if(bufferIndex != INVALID_INDEX && context.mOutput.mResources.mBuffers[bufferIndex].IsAvailable())
+    {
+      auto& stream = context.mOutput.mResources.mBuffers[bufferIndex].GetBufferStream();
+      stream.clear();
+      stream.seekg(mm.mTexture->mSource->mBufferView->mByteOffset, stream.beg);
+      std::vector<uint8_t> dataBuffer;
+      dataBuffer.resize(mm.mTexture->mSource->mBufferView->mByteLength);
+      stream.read(reinterpret_cast<char*>(dataBuffer.data()), static_cast<std::streamsize>(static_cast<size_t>(mm.mTexture->mSource->mBufferView->mByteLength)));
+      return TextureDefinition{std::move(dataBuffer), ConvertSampler(mm.mTexture->mSampler), metaData.mMinSize, metaData.mSamplingMode};
+    }
+    return TextureDefinition();
+  }
+  else
+  {
+    return TextureDefinition{uri, ConvertSampler(mm.mTexture->mSampler), metaData.mMinSize, metaData.mSamplingMode};
+  }
+}
+
+void ConvertMaterial(const gt::Material& material, const std::unordered_map<std::string, ImageMetadata>& imageMetaData, decltype(ResourceBundle::mMaterials)& outMaterials, ConversionContext& context)
+{
+  auto getTextureMetaData = [](const std::unordered_map<std::string, ImageMetadata>& metaData, const gt::TextureInfo& info)
+  {
+    if(!info.mTexture->mSource->mUri.empty())
+    {
+      if(auto search = metaData.find(info.mTexture->mSource->mUri.data()); search != metaData.end())
+      {
+        return search->second;
+      }
+    }
+    return ImageMetadata();
+  };
+
+  MaterialDefinition matDef;
+
+  auto& pbr = material.mPbrMetallicRoughness;
+  if(material.mAlphaMode == gt::AlphaMode::BLEND)
+  {
+    matDef.mIsOpaque = false;
+    matDef.mFlags |= MaterialDefinition::TRANSPARENCY;
+  }
+  else if(material.mAlphaMode == gt::AlphaMode::MASK)
+  {
+    matDef.mIsMask = true;
+    matDef.SetAlphaCutoff(std::min(1.f, std::max(0.f, material.mAlphaCutoff)));
+  }
+
+  matDef.mBaseColorFactor = pbr.mBaseColorFactor;
+
+  matDef.mTextureStages.reserve(!!pbr.mBaseColorTexture + !!pbr.mMetallicRoughnessTexture + !!material.mNormalTexture + !!material.mOcclusionTexture + !!material.mEmissiveTexture);
+  if(pbr.mBaseColorTexture)
+  {
+    const auto semantic = MaterialDefinition::ALBEDO;
+    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(pbr.mBaseColorTexture, context, getTextureMetaData(imageMetaData, pbr.mBaseColorTexture))});
+    // TODO: and there had better be one
+    matDef.mFlags |= semantic;
+  }
+  else
+  {
+    matDef.mNeedAlbedoTexture = false;
+  }
+
+  matDef.mMetallic  = pbr.mMetallicFactor;
+  matDef.mRoughness = pbr.mRoughnessFactor;
+
+  if(pbr.mMetallicRoughnessTexture)
+  {
+    const auto semantic = MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS |
+                          MaterialDefinition::GLTF_CHANNELS;
+    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(pbr.mMetallicRoughnessTexture, context, getTextureMetaData(imageMetaData, pbr.mMetallicRoughnessTexture))});
+    // TODO: and there had better be one
+    matDef.mFlags |= semantic;
+  }
+  else
+  {
+    matDef.mNeedMetallicRoughnessTexture = false;
+  }
+
+  matDef.mNormalScale = material.mNormalTexture.mScale;
+  if(material.mNormalTexture)
+  {
+    const auto semantic = MaterialDefinition::NORMAL;
+    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mNormalTexture, context, getTextureMetaData(imageMetaData, material.mNormalTexture))});
+    // TODO: and there had better be one
+    matDef.mFlags |= semantic;
+  }
+  else
+  {
+    matDef.mNeedNormalTexture = false;
+  }
+
+  if(material.mOcclusionTexture)
+  {
+    const auto semantic = MaterialDefinition::OCCLUSION;
+    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mOcclusionTexture, context, getTextureMetaData(imageMetaData, material.mOcclusionTexture))});
+    // TODO: and there had better be one
+    matDef.mFlags |= semantic;
+    matDef.mOcclusionStrength = material.mOcclusionTexture.mStrength;
+  }
+
+  if(material.mEmissiveTexture)
+  {
+    const auto semantic = MaterialDefinition::EMISSIVE;
+    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mEmissiveTexture, context, getTextureMetaData(imageMetaData, material.mEmissiveTexture))});
+    // TODO: and there had better be one
+    matDef.mFlags |= semantic;
+    matDef.mEmissiveFactor = material.mEmissiveFactor;
+  }
+
+  if(!Dali::Equals(material.mMaterialExtensions.mMaterialIor.mIor, gltf2::UNDEFINED_FLOAT_VALUE))
+  {
+    float ior                  = material.mMaterialExtensions.mMaterialIor.mIor;
+    matDef.mDielectricSpecular = powf((ior - 1.0f) / (ior + 1.0f), 2.0f);
+  }
+  matDef.mSpecularFactor      = material.mMaterialExtensions.mMaterialSpecular.mSpecularFactor;
+  matDef.mSpecularColorFactor = material.mMaterialExtensions.mMaterialSpecular.mSpecularColorFactor;
+
+  if(material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture)
+  {
+    const auto semantic = MaterialDefinition::SPECULAR;
+    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture, context, getTextureMetaData(imageMetaData, material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture))});
+    matDef.mFlags |= semantic;
+  }
+
+  if(material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture)
+  {
+    const auto semantic = MaterialDefinition::SPECULAR_COLOR;
+    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture, context, getTextureMetaData(imageMetaData, material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture))});
+    matDef.mFlags |= semantic;
+  }
+
+  matDef.mDoubleSided = material.mDoubleSided;
+
+  outMaterials.emplace_back(std::move(matDef), TextureSet());
+}
+
+void ConvertMaterials(const gt::Document& doc, ConversionContext& context)
+{
+  auto& imageMetaData = context.mOutput.mSceneMetadata.mImageMetadata;
+
+  auto& outMaterials = context.mOutput.mResources.mMaterials;
+  outMaterials.reserve(doc.mMaterials.size());
+
+  for(auto& m : doc.mMaterials)
+  {
+    ConvertMaterial(m, imageMetaData, outMaterials, context);
+  }
+}
+
+MeshDefinition::Accessor ConvertMeshPrimitiveAccessor(const gt::Accessor& acc)
+{
+  DALI_ASSERT_ALWAYS((acc.mBufferView &&
+                      (acc.mBufferView->mByteStride < std::numeric_limits<uint16_t>::max())) ||
+                     (acc.mSparse && !acc.mBufferView));
+
+  DALI_ASSERT_ALWAYS(!acc.mSparse ||
+                     ((acc.mSparse->mIndices.mBufferView && (acc.mSparse->mIndices.mBufferView->mByteStride < std::numeric_limits<uint16_t>::max())) &&
+                      (acc.mSparse->mValues.mBufferView && (acc.mSparse->mValues.mBufferView->mByteStride < std::numeric_limits<uint16_t>::max()))));
+
+  MeshDefinition::SparseBlob sparseBlob;
+  if(acc.mSparse)
+  {
+    const gt::Accessor::Sparse&               sparse  = *acc.mSparse;
+    const gt::ComponentTypedBufferViewClient& indices = sparse.mIndices;
+    const gt::BufferViewClient&               values  = sparse.mValues;
+
+    MeshDefinition::Blob indicesBlob(
+      indices.mBufferView->mByteOffset + indices.mByteOffset,
+      sparse.mCount * indices.GetBytesPerComponent(),
+      static_cast<uint16_t>(indices.mBufferView->mByteStride),
+      static_cast<uint16_t>(indices.GetBytesPerComponent()),
+      {},
+      {});
+    MeshDefinition::Blob valuesBlob(
+      values.mBufferView->mByteOffset + values.mByteOffset,
+      sparse.mCount * acc.GetElementSizeBytes(),
+      static_cast<uint16_t>(values.mBufferView->mByteStride),
+      static_cast<uint16_t>(acc.GetElementSizeBytes()),
+      {},
+      {});
+
+    sparseBlob = std::move(MeshDefinition::SparseBlob(std::move(indicesBlob), std::move(valuesBlob), acc.mSparse->mCount));
+  }
+
+  uint32_t bufferViewOffset = 0u;
+  uint32_t bufferViewStride = 0u;
+  if(acc.mBufferView)
+  {
+    bufferViewOffset = acc.mBufferView->mByteOffset;
+    bufferViewStride = acc.mBufferView->mByteStride;
+  }
+
+  return MeshDefinition::Accessor{
+    std::move(MeshDefinition::Blob{bufferViewOffset + acc.mByteOffset,
+                                   acc.GetBytesLength(),
+                                   static_cast<uint16_t>(bufferViewStride),
+                                   static_cast<uint16_t>(acc.GetElementSizeBytes()),
+                                   acc.mMin,
+                                   acc.mMax}),
+    std::move(sparseBlob),
+    acc.mBufferView ? acc.mBufferView->mBuffer.GetIndex() : 0};
+}
+
+void ConvertMeshes(const gt::Document& doc, ConversionContext& context)
+{
+  uint32_t meshCount = 0;
+  context.mMeshIds.reserve(doc.mMeshes.size());
+  for(auto& mesh : doc.mMeshes)
+  {
+    context.mMeshIds.push_back(meshCount);
+    meshCount += mesh.mPrimitives.size();
+  }
+
+  auto& outMeshes = context.mOutput.mResources.mMeshes;
+  outMeshes.reserve(meshCount);
+  for(auto& mesh : doc.mMeshes)
+  {
+    for(auto& primitive : mesh.mPrimitives)
+    {
+      MeshDefinition meshDefinition;
+
+      auto& attribs                 = primitive.mAttributes;
+      meshDefinition.mPrimitiveType = GLTF2_TO_DALI_PRIMITIVES[primitive.mMode];
+
+      auto& accPositions        = *attribs.find(gt::Attribute::POSITION)->second;
+      meshDefinition.mPositions = ConvertMeshPrimitiveAccessor(accPositions);
+      // glTF2 support vector4 tangent for mesh.
+      // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#meshes-overview
+      meshDefinition.mTangentType = Property::VECTOR4;
+
+      const bool needNormalsTangents = accPositions.mType == gt::AccessorType::VEC3;
+      for(auto& am : ATTRIBUTE_MAPPINGS)
+      {
+        auto iFind = attribs.find(am.mType);
+        if(iFind != attribs.end())
+        {
+          auto& accessor = meshDefinition.*(am.mAccessor);
+          accessor       = ConvertMeshPrimitiveAccessor(*iFind->second);
+
+          if(iFind->first == gt::Attribute::JOINTS_0)
+          {
+            meshDefinition.mFlags |= (iFind->second->mComponentType == gt::Component::UNSIGNED_SHORT) * MeshDefinition::U16_JOINT_IDS;
+            meshDefinition.mFlags |= (iFind->second->mComponentType == gt::Component::UNSIGNED_BYTE) * MeshDefinition::U8_JOINT_IDS;
+            DALI_ASSERT_DEBUG(MaskMatch(meshDefinition.mFlags, MeshDefinition::U16_JOINT_IDS) || MaskMatch(meshDefinition.mFlags, MeshDefinition::U8_JOINT_IDS) || iFind->second->mComponentType == gt::Component::FLOAT);
+          }
+        }
+        else if(needNormalsTangents)
+        {
+          switch(am.mType)
+          {
+            case gt::Attribute::NORMAL:
+              meshDefinition.RequestNormals();
+              break;
+
+            case gt::Attribute::TANGENT:
+              meshDefinition.RequestTangents();
+              break;
+
+            default:
+              break;
+          }
+        }
+      }
+
+      if(primitive.mIndices)
+      {
+        meshDefinition.mIndices = ConvertMeshPrimitiveAccessor(*primitive.mIndices);
+        meshDefinition.mFlags |= (primitive.mIndices->mComponentType == gt::Component::UNSIGNED_INT) * MeshDefinition::U32_INDICES;
+        meshDefinition.mFlags |= (primitive.mIndices->mComponentType == gt::Component::UNSIGNED_BYTE) * MeshDefinition::U8_INDICES;
+        DALI_ASSERT_DEBUG(MaskMatch(meshDefinition.mFlags, MeshDefinition::U32_INDICES) || MaskMatch(meshDefinition.mFlags, MeshDefinition::U8_INDICES) || primitive.mIndices->mComponentType == gt::Component::UNSIGNED_SHORT);
+      }
+
+      if(!primitive.mTargets.empty())
+      {
+        meshDefinition.mBlendShapes.reserve(primitive.mTargets.size());
+        meshDefinition.mBlendShapeVersion = BlendShapes::Version::VERSION_2_0;
+        for(const auto& target : primitive.mTargets)
+        {
+          MeshDefinition::BlendShape blendShape;
+
+          auto endIt = target.end();
+          auto it    = target.find(gt::Attribute::POSITION);
+          if(it != endIt)
+          {
+            blendShape.deltas = ConvertMeshPrimitiveAccessor(*it->second);
+          }
+          it = target.find(gt::Attribute::NORMAL);
+          if(it != endIt)
+          {
+            blendShape.normals = ConvertMeshPrimitiveAccessor(*it->second);
+          }
+          it = target.find(gt::Attribute::TANGENT);
+          if(it != endIt)
+          {
+            blendShape.tangents = ConvertMeshPrimitiveAccessor(*it->second);
+          }
+
+          if(!mesh.mWeights.empty())
+          {
+            blendShape.weight = mesh.mWeights[meshDefinition.mBlendShapes.size()];
+          }
+
+          meshDefinition.mBlendShapes.push_back(std::move(blendShape));
+        }
+      }
+
+      outMeshes.push_back({std::move(meshDefinition), MeshGeometry{}});
+    }
+  }
+}
+
+ModelRenderable* MakeModelRenderable(const gt::Mesh::Primitive& prim, ConversionContext& context)
+{
+  auto modelRenderable = new ModelRenderable();
+
+  modelRenderable->mShaderIdx = 0; // TODO: further thought
+
+  auto materialIdx = prim.mMaterial.GetIndex();
+  if(INVALID_INDEX == materialIdx)
+  {
+    // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#default-material
+    if(INVALID_INDEX == context.mDefaultMaterial)
+    {
+      auto& outMaterials       = context.mOutput.mResources.mMaterials;
+      context.mDefaultMaterial = outMaterials.size();
+
+      ConvertMaterial(gt::Material{}, context.mOutput.mSceneMetadata.mImageMetadata, outMaterials, context);
+    }
+
+    materialIdx = context.mDefaultMaterial;
+  }
+
+  modelRenderable->mMaterialIdx = materialIdx;
+
+  return modelRenderable;
+}
+
+void ConvertCamera(const gt::Camera& camera, CameraParameters& camParams)
+{
+  camParams.isPerspective = camera.mType.compare("perspective") == 0;
+  if(camParams.isPerspective)
+  {
+    auto& perspective = camera.mPerspective;
+    if(!Dali::Equals(perspective.mYFov, gltf2::UNDEFINED_FLOAT_VALUE))
+    {
+      camParams.yFovDegree = Degree(Radian(perspective.mYFov));
+    }
+    else
+    {
+      camParams.yFovDegree = Degree(gltf2::UNDEFINED_FLOAT_VALUE);
+    }
+    camParams.zNear = perspective.mZNear;
+    camParams.zFar  = perspective.mZFar;
+    // TODO: yes, we seem to ignore aspectRatio in CameraParameters.
+  }
+  else
+  {
+    auto& ortho = camera.mOrthographic;
+    if(!Dali::Equals(ortho.mYMag, gltf2::UNDEFINED_FLOAT_VALUE) && !Dali::Equals(ortho.mXMag, gltf2::UNDEFINED_FLOAT_VALUE))
+    {
+      camParams.orthographicSize = ortho.mYMag * .5f;
+      camParams.aspectRatio      = ortho.mXMag / ortho.mYMag;
+    }
+    else
+    {
+      camParams.orthographicSize = gltf2::UNDEFINED_FLOAT_VALUE;
+      camParams.aspectRatio      = gltf2::UNDEFINED_FLOAT_VALUE;
+    }
+    camParams.zNear = ortho.mZNear;
+    camParams.zFar  = ortho.mZFar;
+  }
+}
+
+void ConvertNode(gt::Node const& node, const Index gltfIdx, Index parentIdx, ConversionContext& context, bool isMRendererModel)
+{
+  auto& output    = context.mOutput;
+  auto& scene     = output.mScene;
+  auto& resources = output.mResources;
+
+  const auto idx      = scene.GetNodeCount();
+  auto       weakNode = scene.AddNode([&]()
+                                {
+    std::unique_ptr<NodeDefinition> nodeDef{new NodeDefinition()};
+
+    nodeDef->mParentIdx = parentIdx;
+    nodeDef->mName      = node.mName;
+    if(nodeDef->mName.empty())
+    {
+      // TODO: Production quality generation of unique names.
+      nodeDef->mName = std::to_string(reinterpret_cast<uintptr_t>(nodeDef.get()));
+    }
+
+    if(!node.mSkin) // Nodes with skinned meshes are not supposed to have local transforms.
+    {
+      nodeDef->mPosition    = node.mTranslation;
+      nodeDef->mOrientation = node.mRotation;
+      nodeDef->mScale       = node.mScale;
+
+      if(isMRendererModel && node.mName == ROOT_NODE_NAME && node.mScale == SCALE_TO_ADJUST)
+      {
+        nodeDef->mScale *= 0.01f;
+      }
+    }
+
+    return nodeDef; }());
+  if(!weakNode)
+  {
+    ExceptionFlinger(ASSERT_LOCATION) << "Node name '" << node.mName << "' is not unique; scene is invalid.";
+  }
+
+  context.mNodeIndices.RegisterMapping(gltfIdx, idx);
+
+  Index skeletonIdx = node.mSkin ? node.mSkin.GetIndex() : INVALID_INDEX;
+  if(node.mMesh)
+  {
+    auto&    mesh           = *node.mMesh;
+    uint32_t primitiveCount = mesh.mPrimitives.size();
+    auto     meshIdx        = context.mMeshIds[node.mMesh.GetIndex()];
+    weakNode->mRenderables.reserve(primitiveCount);
+    for(uint32_t i = 0; i < primitiveCount; ++i)
+    {
+      std::unique_ptr<NodeDefinition::Renderable> renderable;
+      auto                                        modelRenderable = MakeModelRenderable(mesh.mPrimitives[i], context);
+      modelRenderable->mMeshIdx                                   = meshIdx + i;
+
+      DALI_ASSERT_DEBUG(resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx == INVALID_INDEX ||
+                        resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx == skeletonIdx);
+      resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx = skeletonIdx;
+
+      renderable.reset(modelRenderable);
+      weakNode->mRenderables.push_back(std::move(renderable));
+    }
+  }
+
+  if(node.mCamera)
+  {
+    CameraParameters camParams;
+    ConvertCamera(*node.mCamera, camParams);
+
+    camParams.matrix.SetTransformComponents(node.mScale, node.mRotation, node.mTranslation);
+    output.mCameraParameters.push_back(camParams);
+  }
+
+  for(auto& n : node.mChildren)
+  {
+    ConvertNode(*n, n.GetIndex(), idx, context, isMRendererModel);
+  }
+}
+
+void ConvertSceneNodes(const gt::Scene& scene, ConversionContext& context, bool isMRendererModel)
+{
+  auto& outScene = context.mOutput.mScene;
+  Index rootIdx  = outScene.GetNodeCount();
+  switch(scene.mNodes.size())
+  {
+    case 0:
+      break;
+
+    case 1:
+      ConvertNode(*scene.mNodes[0], scene.mNodes[0].GetIndex(), INVALID_INDEX, context, isMRendererModel);
+      outScene.AddRootNode(rootIdx);
+      break;
+
+    default:
+    {
+      std::unique_ptr<NodeDefinition> sceneRoot{new NodeDefinition()};
+      sceneRoot->mName = "GLTF_LOADER_SCENE_ROOT_" + std::to_string(outScene.GetRoots().size());
+
+      outScene.AddNode(std::move(sceneRoot));
+      outScene.AddRootNode(rootIdx);
+
+      for(auto& n : scene.mNodes)
+      {
+        ConvertNode(*n, n.GetIndex(), rootIdx, context, isMRendererModel);
+      }
+      break;
+    }
+  }
+}
+
+void ConvertNodes(const gt::Document& doc, ConversionContext& context, bool isMRendererModel)
+{
+  if(!doc.mScenes.empty())
+  {
+    uint32_t rootSceneIndex = 0u;
+    if(doc.mScene)
+    {
+      rootSceneIndex = doc.mScene.GetIndex();
+    }
+    ConvertSceneNodes(doc.mScenes[rootSceneIndex], context, isMRendererModel);
+
+    for(uint32_t i = 0, i1 = rootSceneIndex; i < i1; ++i)
+    {
+      ConvertSceneNodes(doc.mScenes[i], context, isMRendererModel);
+    }
+
+    for(uint32_t i = rootSceneIndex + 1; i < doc.mScenes.size(); ++i)
+    {
+      ConvertSceneNodes(doc.mScenes[i], context, isMRendererModel);
+    }
+  }
+}
+
+template<typename T>
+void LoadDataFromAccessor(ConversionContext& context, uint32_t bufferIndex, Vector<T>& dataBuffer, uint32_t offset, uint32_t size)
+{
+  if(bufferIndex >= context.mOutput.mResources.mBuffers.size())
+  {
+    DALI_LOG_ERROR("Invailid buffer index\n");
+    return;
+  }
+
+  auto& buffer = context.mOutput.mResources.mBuffers[bufferIndex];
+  if(!buffer.IsAvailable())
+  {
+    DALI_LOG_ERROR("Failed to load from buffer stream.\n");
+  }
+  auto& stream = buffer.GetBufferStream();
+  stream.clear();
+  stream.seekg(offset, stream.beg);
+  stream.read(reinterpret_cast<char*>(dataBuffer.Begin()), static_cast<std::streamsize>(static_cast<size_t>(size)));
+}
+
+template<typename T>
+float LoadDataFromAccessors(ConversionContext& context, const gltf2::Accessor& input, const gltf2::Accessor& output, Vector<float>& inputDataBuffer, Vector<T>& outputDataBuffer)
+{
+  inputDataBuffer.Resize(input.mCount);
+  outputDataBuffer.Resize(output.mCount);
+
+  const uint32_t inputDataBufferSize  = input.GetBytesLength();
+  const uint32_t outputDataBufferSize = output.GetBytesLength();
+
+  LoadDataFromAccessor<float>(context, output.mBufferView->mBuffer.GetIndex(), inputDataBuffer, input.mBufferView->mByteOffset + input.mByteOffset, inputDataBufferSize);
+  LoadDataFromAccessor<T>(context, output.mBufferView->mBuffer.GetIndex(), outputDataBuffer, output.mBufferView->mByteOffset + output.mByteOffset, outputDataBufferSize);
+  ApplyAccessorMinMax(input, reinterpret_cast<float*>(inputDataBuffer.begin()));
+  ApplyAccessorMinMax(output, reinterpret_cast<float*>(outputDataBuffer.begin()));
+
+  return inputDataBuffer[input.mCount - 1u];
+}
+
+template<typename T>
+float LoadKeyFrames(ConversionContext& context, const gt::Animation::Channel& channel, KeyFrames& keyFrames, gt::Animation::Channel::Target::Type type)
+{
+  const gltf2::Accessor& input  = *channel.mSampler->mInput;
+  const gltf2::Accessor& output = *channel.mSampler->mOutput;
+
+  Vector<float> inputDataBuffer;
+  Vector<T>     outputDataBuffer;
+
+  const float duration = std::max(LoadDataFromAccessors<T>(context, input, output, inputDataBuffer, outputDataBuffer), AnimationDefinition::MIN_DURATION_SECONDS);
+
+  // Set first frame value as first keyframe (gltf animation spec)
+  if(input.mCount > 0 && !Dali::EqualsZero(inputDataBuffer[0]))
+  {
+    keyFrames.Add(0.0f, outputDataBuffer[0]);
+  }
+
+  for(uint32_t i = 0; i < input.mCount; ++i)
+  {
+    keyFrames.Add(inputDataBuffer[i] / duration, outputDataBuffer[i]);
+  }
+
+  return duration;
+}
+
+float LoadBlendShapeKeyFrames(ConversionContext& context, const gt::Animation::Channel& channel, Index nodeIndex, uint32_t& propertyIndex, std::vector<Dali::Scene3D::Loader::AnimatedProperty>& properties)
+{
+  const gltf2::Accessor& input  = *channel.mSampler->mInput;
+  const gltf2::Accessor& output = *channel.mSampler->mOutput;
+
+  Vector<float> inputDataBuffer;
+  Vector<float> outputDataBuffer;
+
+  const float duration = std::max(LoadDataFromAccessors<float>(context, input, output, inputDataBuffer, outputDataBuffer), AnimationDefinition::MIN_DURATION_SECONDS);
+
+  char        weightNameBuffer[32];
+  auto        prefixSize    = snprintf(weightNameBuffer, sizeof(weightNameBuffer), "%s[", BLEND_SHAPE_WEIGHTS_UNIFORM.c_str());
+  char* const pWeightName   = weightNameBuffer + prefixSize;
+  const auto  remainingSize = sizeof(weightNameBuffer) - prefixSize;
+  for(uint32_t weightIndex = 0u, endWeightIndex = channel.mSampler->mOutput->mCount / channel.mSampler->mInput->mCount; weightIndex < endWeightIndex; ++weightIndex)
+  {
+    AnimatedProperty& animatedProperty = properties[propertyIndex++];
+
+    animatedProperty.mNodeIndex = nodeIndex;
+    snprintf(pWeightName, remainingSize, "%d]", weightIndex);
+    animatedProperty.mPropertyName = std::string(weightNameBuffer);
+
+    animatedProperty.mKeyFrames = KeyFrames::New();
+
+    // Set first frame value as first keyframe (gltf animation spec)
+    if(input.mCount > 0 && !Dali::EqualsZero(inputDataBuffer[0]))
+    {
+      animatedProperty.mKeyFrames.Add(0.0f, outputDataBuffer[weightIndex]);
+    }
+
+    for(uint32_t i = 0; i < input.mCount; ++i)
+    {
+      animatedProperty.mKeyFrames.Add(inputDataBuffer[i] / duration, outputDataBuffer[i * endWeightIndex + weightIndex]);
+    }
+
+    animatedProperty.mTimePeriod = {0.f, duration};
+  }
+
+  return duration;
+}
+
+void ConvertAnimations(const gt::Document& doc, ConversionContext& context)
+{
+  auto& output = context.mOutput;
+
+  output.mAnimationDefinitions.reserve(output.mAnimationDefinitions.size() + doc.mAnimations.size());
+
+  for(const auto& animation : doc.mAnimations)
+  {
+    AnimationDefinition animationDef;
+
+    if(!animation.mName.empty())
+    {
+      animationDef.mName = animation.mName;
+    }
+
+    uint32_t numberOfProperties = 0u;
+    for(const auto& channel : animation.mChannels)
+    {
+      if(channel.mTarget.mPath == gt::Animation::Channel::Target::WEIGHTS)
+      {
+        numberOfProperties += channel.mSampler->mOutput->mCount / channel.mSampler->mInput->mCount;
+      }
+      else
+      {
+        numberOfProperties++;
+      }
+    }
+    animationDef.mProperties.resize(numberOfProperties);
+
+    Index propertyIndex = 0u;
+    for(const auto& channel : animation.mChannels)
+    {
+      Index nodeIndex = context.mNodeIndices.GetRuntimeId(channel.mTarget.mNode.GetIndex());
+      float duration  = 0.f;
+
+      switch(channel.mTarget.mPath)
+      {
+        case gt::Animation::Channel::Target::TRANSLATION:
+        {
+          AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex];
+
+          animatedProperty.mNodeIndex    = nodeIndex;
+          animatedProperty.mPropertyName = POSITION_PROPERTY;
+
+          animatedProperty.mKeyFrames = KeyFrames::New();
+          duration                    = LoadKeyFrames<Vector3>(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath);
+
+          animatedProperty.mTimePeriod = {0.f, duration};
+          break;
+        }
+        case gt::Animation::Channel::Target::ROTATION:
+        {
+          AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex];
+
+          animatedProperty.mNodeIndex    = nodeIndex;
+          animatedProperty.mPropertyName = ORIENTATION_PROPERTY;
+
+          animatedProperty.mKeyFrames = KeyFrames::New();
+          duration                    = LoadKeyFrames<Quaternion>(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath);
+
+          animatedProperty.mTimePeriod = {0.f, duration};
+          break;
+        }
+        case gt::Animation::Channel::Target::SCALE:
+        {
+          AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex];
+
+          animatedProperty.mNodeIndex    = nodeIndex;
+          animatedProperty.mPropertyName = SCALE_PROPERTY;
+
+          animatedProperty.mKeyFrames = KeyFrames::New();
+          duration                    = LoadKeyFrames<Vector3>(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath);
+
+          animatedProperty.mTimePeriod = {0.f, duration};
+          break;
+        }
+        case gt::Animation::Channel::Target::WEIGHTS:
+        {
+          duration = LoadBlendShapeKeyFrames(context, channel, nodeIndex, propertyIndex, animationDef.mProperties);
+
+          break;
+        }
+        default:
+        {
+          // nothing to animate.
+          break;
+        }
+      }
+
+      animationDef.mDuration = std::max(duration, animationDef.mDuration);
+
+      ++propertyIndex;
+    }
+
+    output.mAnimationDefinitions.push_back(std::move(animationDef));
+  }
+}
+
+void ProcessSkins(const gt::Document& doc, ConversionContext& context)
+{
+  // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skininversebindmatrices
+  // If an inverseBindMatrices accessor was provided, we'll load the joint data from the buffer,
+  // otherwise we'll set identity matrices for inverse bind pose.
+  struct IInverseBindMatrixProvider
+  {
+    virtual ~IInverseBindMatrixProvider()
+    {
+    }
+    virtual void Provide(Matrix& ibm) = 0;
+  };
+
+  struct InverseBindMatrixAccessor : public IInverseBindMatrixProvider
+  {
+    std::istream&  mStream;
+    const uint32_t mElementSizeBytes;
+
+    InverseBindMatrixAccessor(const gt::Accessor& accessor, ConversionContext& context)
+    : mStream(context.mOutput.mResources.mBuffers[accessor.mBufferView->mBuffer.GetIndex()].GetBufferStream()),
+      mElementSizeBytes(accessor.GetElementSizeBytes())
+    {
+      DALI_ASSERT_DEBUG(accessor.mType == gt::AccessorType::MAT4 && accessor.mComponentType == gt::Component::FLOAT);
+
+      if(!mStream.rdbuf()->in_avail())
+      {
+        DALI_LOG_ERROR("Failed to load from stream\n");
+      }
+      mStream.clear();
+      mStream.seekg(accessor.mBufferView->mByteOffset + accessor.mByteOffset, mStream.beg);
+    }
+
+    virtual void Provide(Matrix& ibm) override
+    {
+      DALI_ASSERT_ALWAYS(mStream.read(reinterpret_cast<char*>(ibm.AsFloat()), static_cast<std::streamsize>(static_cast<size_t>(mElementSizeBytes))));
+    }
+  };
+
+  struct DefaultInverseBindMatrixProvider : public IInverseBindMatrixProvider
+  {
+    virtual void Provide(Matrix& ibm) override
+    {
+      ibm = Matrix::IDENTITY;
+    }
+  };
+
+  auto& resources = context.mOutput.mResources;
+  resources.mSkeletons.reserve(doc.mSkins.size());
+
+  for(auto& skin : doc.mSkins)
+  {
+    std::unique_ptr<IInverseBindMatrixProvider> ibmProvider;
+    if(skin.mInverseBindMatrices)
+    {
+      ibmProvider.reset(new InverseBindMatrixAccessor(*skin.mInverseBindMatrices, context));
+    }
+    else
+    {
+      ibmProvider.reset(new DefaultInverseBindMatrixProvider());
+    }
+
+    SkeletonDefinition skeleton;
+    if(skin.mSkeleton.GetIndex() != INVALID_INDEX)
+    {
+      skeleton.mRootNodeIdx = context.mNodeIndices.GetRuntimeId(skin.mSkeleton.GetIndex());
+    }
+
+    skeleton.mJoints.resize(skin.mJoints.size());
+    auto iJoint = skeleton.mJoints.begin();
+    for(auto& joint : skin.mJoints)
+    {
+      iJoint->mNodeIdx = context.mNodeIndices.GetRuntimeId(joint.GetIndex());
+
+      ibmProvider->Provide(iJoint->mInverseBindMatrix);
+
+      ++iJoint;
+    }
+
+    resources.mSkeletons.push_back(std::move(skeleton));
+  }
+}
+
+void ProduceShaders(ShaderDefinitionFactory& shaderFactory, SceneDefinition& scene)
+{
+  uint32_t nodeCount = scene.GetNodeCount();
+  for(uint32_t i = 0; i < nodeCount; ++i)
+  {
+    auto nodeDef = scene.GetNode(i);
+    for(auto& renderable : nodeDef->mRenderables)
+    {
+      if(shaderFactory.ProduceShader(*renderable) == INVALID_INDEX)
+      {
+        DALI_LOG_ERROR("Fail to produce shader\n");
+      }
+    }
+  }
+}
+
+void SetObjectReaders()
+{
+  js::SetObjectReader(BUFFER_READER);
+  js::SetObjectReader(BUFFER_VIEW_READER);
+  js::SetObjectReader(BUFFER_VIEW_CLIENT_READER);
+  js::SetObjectReader(COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER);
+  js::SetObjectReader(ACCESSOR_SPARSE_READER);
+  js::SetObjectReader(ACCESSOR_READER);
+  js::SetObjectReader(IMAGE_READER);
+  js::SetObjectReader(SAMPLER_READER);
+  js::SetObjectReader(TEXURE_READER);
+  js::SetObjectReader(TEXURE_INFO_READER);
+  js::SetObjectReader(MATERIAL_PBR_READER);
+  js::SetObjectReader(MATERIAL_SPECULAR_READER);
+  js::SetObjectReader(MATERIAL_IOR_READER);
+  js::SetObjectReader(MATERIAL_EXTENSION_READER);
+  js::SetObjectReader(MATERIAL_READER);
+  js::SetObjectReader(MESH_PRIMITIVE_READER);
+  js::SetObjectReader(MESH_READER);
+  js::SetObjectReader(SKIN_READER);
+  js::SetObjectReader(CAMERA_PERSPECTIVE_READER);
+  js::SetObjectReader(CAMERA_ORTHOGRAPHIC_READER);
+  js::SetObjectReader(CAMERA_READER);
+  js::SetObjectReader(NODE_READER);
+  js::SetObjectReader(ANIMATION_SAMPLER_READER);
+  js::SetObjectReader(ANIMATION_TARGET_READER);
+  js::SetObjectReader(ANIMATION_CHANNEL_READER);
+  js::SetObjectReader(ANIMATION_READER);
+  js::SetObjectReader(SCENE_READER);
+}
+
+void SetDefaultEnvironmentMap(const gt::Document& doc, ConversionContext& context)
+{
+  EnvironmentDefinition envDef;
+  envDef.mUseBrdfTexture = true;
+  envDef.mIblIntensity   = Scene3D::Loader::EnvironmentDefinition::GetDefaultIntensity();
+  context.mOutput.mResources.mEnvironmentMaps.push_back({std::move(envDef), EnvironmentDefinition::Textures()});
+}
+
+} // namespace
+
+void Gltf2LoaderImpl::InitializeGltfLoader()
+{
+  static Dali::Mutex mInitializeMutex;
+  // Set ObjectReader only once (for all gltf loading).
+  static bool setObjectReadersRequired = true;
+  {
+    Mutex::ScopedLock lock(mInitializeMutex);
+    if(setObjectReadersRequired)
+    {
+      // NOTE: only referencing own, anonymous namespace, const objects; the pointers will never need to change.
+      SetObjectReaders();
+      setObjectReadersRequired = false;
+    }
+  }
+}
+
+bool Gltf2LoaderImpl::LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result)
+{
+  bool failed = false;
+  auto js     = LoadTextFile(url.c_str(), &failed);
+  if(failed)
+  {
+    DALI_LOG_ERROR("Failed to load %s\n", url.c_str());
+    return false;
+  }
+
+  json::unique_ptr root(json_parse(js.c_str(), js.size()));
+  if(!root)
+  {
+    DALI_LOG_ERROR("Failed to parse %s\n", url.c_str());
+    return false;
+  }
+
+  gt::Document doc;
+
+  Dali::Scene3D::Loader::ShaderDefinitionFactory shaderFactory;
+  shaderFactory.SetResources(result.mResources);
+
+  auto& rootObj = js::Cast<json_object_s>(*root);
+  auto  jsAsset = js::FindObjectChild("asset", rootObj);
+
+  auto jsAssetVersion = js::FindObjectChild("version", js::Cast<json_object_s>(*jsAsset));
+  if(jsAssetVersion)
+  {
+    doc.mAsset.mVersion = js::Read::StringView(*jsAssetVersion);
+  }
+
+  bool isMRendererModel(false);
+  auto jsAssetGenerator = js::FindObjectChild("generator", js::Cast<json_object_s>(*jsAsset));
+  if(jsAssetGenerator)
+  {
+    doc.mAsset.mGenerator = js::Read::StringView(*jsAssetGenerator);
+    isMRendererModel      = (doc.mAsset.mGenerator.find(MRENDERER_MODEL_IDENTIFICATION) != std::string_view::npos);
+  }
+
+  InitializeGltfLoader();
+  {
+    static Dali::Mutex mReadMutex;
+    Mutex::ScopedLock  lock(mReadMutex);
+    gt::SetRefReaderObject(doc);
+    DOCUMENT_READER.Read(rootObj, doc);
+  }
+
+  auto              path = url.substr(0, url.rfind('/') + 1);
+  ConversionContext context{result, path, INVALID_INDEX};
+
+  ConvertBuffers(doc, context);
+  ConvertMaterials(doc, context);
+  ConvertMeshes(doc, context);
+  ConvertNodes(doc, context, isMRendererModel);
+  ConvertAnimations(doc, context);
+  ProcessSkins(doc, context);
+  ProduceShaders(shaderFactory, result.mScene);
+  result.mScene.EnsureUniqueSkinningShaderInstances(result.mResources);
+
+  // Set Default Environment map
+  SetDefaultEnvironmentMap(doc, context);
+
+  return true;
+}
+
+} // namespace Internal
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
diff --git a/dali-scene3d/internal/loader/gltf2-loader-impl.h b/dali-scene3d/internal/loader/gltf2-loader-impl.h
new file mode 100644 (file)
index 0000000..2b96608
--- /dev/null
@@ -0,0 +1,59 @@
+#ifndef DALI_SCENE3D_LOADER_GLTF2_LOADER_IMPL_H
+#define DALI_SCENE3D_LOADER_GLTF2_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/internal/loader/model-loader-impl.h>
+#include <dali-scene3d/public-api/api.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/threading/mutex.h>
+#include <string>
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Loader
+{
+namespace Internal
+{
+
+class Gltf2LoaderImpl : public ModelLoaderImpl
+{
+public:
+
+  /**
+   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl()
+   */
+  bool LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) override;
+
+private:
+  /**
+   * @brief Initialize glTF Loader.
+   * @note This method should be called once before LoadGltfScene() is called.
+   */
+  void InitializeGltfLoader();
+};
+
+} // namespace Internal
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
+
+#endif // DALI_SCENE3D_LOADER_GLTF2_LOADER_IMPL_H
diff --git a/dali-scene3d/internal/loader/model-loader-impl.h b/dali-scene3d/internal/loader/model-loader-impl.h
new file mode 100644 (file)
index 0000000..619cacb
--- /dev/null
@@ -0,0 +1,69 @@
+#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 95e3aee0ad1b0ebfe5cd00517925b6c540cb852f..dffdfc8876ba644db7b27e61686a3830ce204efc 100644 (file)
@@ -13,15 +13,14 @@ set(scene3d_src_files ${scene3d_src_files}
        ${scene3d_public_api_dir}/loader/environment-map-data.cpp
        ${scene3d_public_api_dir}/loader/environment-map-loader.cpp
        ${scene3d_public_api_dir}/loader/customization.cpp
-       ${scene3d_public_api_dir}/loader/dli-loader.cpp
        ${scene3d_public_api_dir}/loader/environment-definition.cpp
        ${scene3d_public_api_dir}/loader/facial-animation-loader.cpp
-       ${scene3d_public_api_dir}/loader/gltf2-loader.cpp
        ${scene3d_public_api_dir}/loader/ktx-loader.cpp
        ${scene3d_public_api_dir}/loader/load-scene-metadata.cpp
        ${scene3d_public_api_dir}/loader/material-definition.cpp
        ${scene3d_public_api_dir}/loader/matrix-stack.cpp
        ${scene3d_public_api_dir}/loader/mesh-definition.cpp
+       ${scene3d_public_api_dir}/loader/model-loader.cpp
        ${scene3d_public_api_dir}/loader/node-definition.cpp
        ${scene3d_public_api_dir}/loader/parse-renderer-state.cpp
        ${scene3d_public_api_dir}/loader/renderer-state.cpp
diff --git a/dali-scene3d/public-api/loader/dli-input-parameter.h b/dali-scene3d/public-api/loader/dli-input-parameter.h
new file mode 100644 (file)
index 0000000..a81ba0c
--- /dev/null
@@ -0,0 +1,95 @@
+#ifndef DALI_SCENE3D_LOADER_DLI_INPUT_PARAMETER_H
+#define DALI_SCENE3D_LOADER_DLI_INPUT_PARAMETER_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/model-loader.h>
+
+// EXTERNAL INCLUDES
+#include <string>
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Loader
+{
+
+class DliInputParameter : public ModelLoader::InputParameter
+{
+public:
+  using ConvertFontCode         = void (*)(const std::string& code, std::string& fontFamily, std::string& slant, std::string& weight, float& size);
+  using ConvertColorCode        = Vector4 (*)(const std::string& code);
+  using CategoryProcessor       = std::function<void(Property::Array&& categoryData, StringCallback onError)>;
+  using CategoryProcessorVector = std::vector<std::pair<std::string /*name*/, CategoryProcessor>>;
+  using NodeProcessor           = std::function<void(const Dali::Scene3D::Loader::NodeDefinition& nodeDef,
+                                           Property::Map&&                              nodeData,
+                                           StringCallback                               onError)>;
+  using AnimationProcessor      = std::function<void(const AnimationDefinition& animDef,
+                                                Property::Map&&            animData,
+                                                StringCallback             onError)>;
+
+public:
+  /**
+   * @brief The absolute path of animation binaries referenced in the .dli.
+   */
+  std::string mAnimationsPath;
+
+  /**
+   * @brief Provides a facility to determine a color from a code instead of RGB(A) values.
+   */
+  ConvertColorCode mConvertColorCode{nullptr};
+
+  /**
+   * @brief A collection of handlers, mapped to the names of the top level (i.e. below
+   *  root) element, whom they will attempt to process. This will take place before
+   *  the parsing of scene Nodes and Animations, but after skeletons, environment, mesh,
+   *  shader and material resources.
+   */
+  CategoryProcessorVector mPreNodeCategoryProcessors;
+
+  /**
+   * @brief A collection of handlers, mapped to the names of the top level (i.e. below
+   *  root) element, whom they will attempt to process. This will take place after
+   *  the parsing of the scene Nodes and Animations.
+   */
+  CategoryProcessorVector mPostNodeCategoryProcessors;
+
+  /**
+   * @brief Provides an extension point to nodes. If provided, this function will be
+   *  called with each JSON element and definition, of a scene node.
+   * @note Constraints rely on ID resolution (from .dli to scene definition), which
+   *  takes place after the parsing of the nodes; therefore AT THIS POINT the node
+   *  IDs seen in constraints will still be the .dli IDs - NOT to be relied on for
+   *  indexing into mScene.
+   */
+  NodeProcessor mNodePropertyProcessor{nullptr};
+
+  /**
+   * @brief Provides an extension point to animations. If provided, this function will be
+   *  called with each JSON element and fully processed definition, of an animation.
+   */
+  AnimationProcessor mAnimationPropertyProcessor;
+};
+
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
+
+#endif // DALI_SCENE3D_LOADER_DLI_INPUT_PARAMETER_H
diff --git a/dali-scene3d/public-api/loader/dli-loader.cpp b/dali-scene3d/public-api/loader/dli-loader.cpp
deleted file mode 100644 (file)
index 536e29b..0000000
+++ /dev/null
@@ -1,1827 +0,0 @@
-/*
- * 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.
- *
- */
-
-// CLASS HEADER
-#include "dali-scene3d/public-api/loader/dli-loader.h"
-
-// EXTERNAL INCLUDES
-#include <algorithm>
-#include <cmath>
-#include <fstream>
-#include <limits>
-#include <memory>
-#include "dali-toolkit/devel-api/builder/json-parser.h"
-#include "dali/devel-api/common/map-wrapper.h"
-#include "dali/integration-api/debug.h"
-#include "dali/public-api/object/property-array.h"
-
-// INTERNAL INCLUDES
-#include "dali-scene3d/internal/loader/json-util.h"
-#include "dali-scene3d/public-api/loader/alpha-function-helper.h"
-#include "dali-scene3d/public-api/loader/animation-definition.h"
-#include "dali-scene3d/public-api/loader/blend-shape-details.h"
-#include "dali-scene3d/public-api/loader/camera-parameters.h"
-#include "dali-scene3d/public-api/loader/ktx-loader.h"
-#include "dali-scene3d/public-api/loader/light-parameters.h"
-#include "dali-scene3d/public-api/loader/load-result.h"
-#include "dali-scene3d/public-api/loader/parse-renderer-state.h"
-#include "dali-scene3d/public-api/loader/scene-definition.h"
-#include "dali-scene3d/public-api/loader/skinning-details.h"
-#include "dali-scene3d/public-api/loader/utils.h"
-
-#define DLI_0_1_COMPATIBILITY
-
-namespace Dali
-{
-using namespace Toolkit;
-
-namespace Scene3D
-{
-namespace Loader
-{
-namespace rs = RendererState;
-
-namespace
-{
-const std::string NODES         = "nodes";
-const std::string SCENES        = "scenes";
-const std::string NODE          = "node";
-const std::string URI           = "uri";
-const std::string URL           = "url";
-const std::string CUSTOMIZATION = "customization";
-const std::string HINTS         = "hints";
-const std::string NAME("name");
-const std::string BLEND_SHAPE_HEADER("blendShapeHeader");
-const std::string BLEND_SHAPES("blendShapes");
-const std::string BLEND_SHAPE_VERSION_1_0("1.0");
-const std::string BLEND_SHAPE_VERSION_2_0("2.0");
-const std::string VERSION("version");
-
-const char* const SHADOW_MAP_SIZE   = "shadowMapSize";
-const char* const ORTHOGRAPHIC_SIZE = "orthographicSize";
-const char* const PIXEL_UNITS       = "px";
-
-const char SLASH = '/';
-
-void ReadModelTransform(const TreeNode* node, Quaternion& orientation, Vector3& translation, Vector3& scale)
-{
-  float num[16u] = {.0f};
-
-  if(ReadVector(node->GetChild("matrix"), num, 16u))
-  {
-    Matrix mat(num);
-    mat.GetTransformComponents(translation, orientation, scale);
-  }
-  else
-  {
-    if(ReadVector(node->GetChild("angle"), num, 3u))
-    {
-      orientation = Quaternion(Radian(Degree(num[0u])), Radian(Degree(num[1u])), Radian(Degree(num[2u])));
-    }
-
-    if(ReadVector(node->GetChild("position"), num, 3u))
-    {
-      translation = Vector3(num);
-    }
-  }
-}
-
-bool ReadAttribBlob(const TreeNode* node, MeshDefinition::Blob& buffer)
-{
-  return ReadBlob(node, buffer.mOffset, buffer.mLength);
-}
-
-bool ReadAttribAccessor(const TreeNode* node, MeshDefinition::Accessor& accessor)
-{
-  return ReadBlob(node, accessor.mBlob.mOffset, accessor.mBlob.mLength);
-}
-
-bool ReadColorCode(const TreeNode* node, Vector4& color, DliLoader::ConvertColorCode convertColorCode)
-{
-  if(!node || !convertColorCode)
-  {
-    return false;
-  }
-
-  color = convertColorCode(node->GetString());
-
-  return true;
-}
-
-bool ReadColorCodeOrColor(const TreeNode* node, Vector4& color, DliLoader::ConvertColorCode convertColorCode)
-{
-  return ReadColorCode(node->GetChild("colorCode"), color, convertColorCode) ||
-         ReadColor(node->GetChild("color"), color);
-}
-
-RendererState::Type ReadRendererState(const TreeNode& tnRendererState)
-{
-  if(tnRendererState.GetType() == TreeNode::INTEGER)
-  {
-    return static_cast<RendererState::Type>(tnRendererState.GetInteger());
-  }
-  else if(tnRendererState.GetType() == TreeNode::STRING)
-  {
-    return RendererState::Parse(tnRendererState.GetString());
-  }
-  else
-  {
-    return -1;
-  }
-}
-
-///@brief Reads arc properties.
-void ReadArcField(const TreeNode* eArc, ArcRenderable& arc)
-{
-  ReadBool(eArc->GetChild("antiAliasing"), arc.mAntiAliasing);
-  ReadInt(eArc->GetChild("arcCaps"), arc.mArcCaps);
-  ReadFloat(eArc->GetChild("radius"), arc.mRadius);
-
-  arc.mStartAngleDegrees = .0f;
-  ReadFloat(eArc->GetChild("startAngle"), arc.mStartAngleDegrees);
-
-  arc.mEndAngleDegrees = .0f;
-  ReadFloat(eArc->GetChild("endAngle"), arc.mEndAngleDegrees);
-}
-
-const TreeNode* GetNthChild(const TreeNode* node, uint32_t index)
-{
-  uint32_t i = 0;
-  for(TreeNode::ConstIterator it = (*node).CBegin(); it != (*node).CEnd(); ++it, ++i)
-  {
-    if(i == index)
-    {
-      return &((*it).second);
-    }
-  }
-  return NULL;
-}
-
-const TreeNode* RequireChild(const TreeNode* node, const std::string& childName)
-{
-  auto child = node->GetChild(childName);
-  if(!child)
-  {
-    ExceptionFlinger flinger(ASSERT_LOCATION);
-    flinger << "Failed to find child node '" << childName << "'";
-    if(auto nodeName = node->GetName())
-    {
-      flinger << " on '" << nodeName << "'";
-    }
-    flinger << ".";
-  }
-  return child;
-}
-
-void ParseProperties(const Toolkit::TreeNode& node, Property::Array& array);
-
-void ParseProperties(const Toolkit::TreeNode& node, Property::Map& map)
-{
-  DALI_ASSERT_DEBUG(node.GetType() == TreeNode::OBJECT);
-  for(auto i0 = node.CBegin(), i1 = node.CEnd(); i0 != i1; ++i0)
-  {
-    auto kv = *i0;
-    switch(kv.second.GetType())
-    {
-      case TreeNode::ARRAY:
-      {
-        Property::Array array;
-        ParseProperties(kv.second, array);
-        map.Insert(kv.first, array);
-        break;
-      }
-
-      case TreeNode::OBJECT:
-      {
-        Property::Map innerMap;
-        ParseProperties(kv.second, innerMap);
-        map.Insert(kv.first, innerMap);
-        break;
-      }
-
-      case TreeNode::STRING:
-      {
-        map.Insert(kv.first, kv.second.GetString());
-        break;
-      }
-
-      case TreeNode::INTEGER:
-      {
-        map.Insert(kv.first, kv.second.GetInteger());
-        break;
-      }
-
-      case TreeNode::BOOLEAN:
-      {
-        map.Insert(kv.first, kv.second.GetBoolean());
-        break;
-      }
-
-      case TreeNode::FLOAT:
-      {
-        map.Insert(kv.first, kv.second.GetFloat());
-        break;
-      }
-
-      case TreeNode::IS_NULL:
-      {
-        break;
-      }
-    }
-  }
-}
-
-void ParseProperties(const Toolkit::TreeNode& node, Property::Array& array)
-{
-  DALI_ASSERT_DEBUG(node.GetType() == TreeNode::ARRAY);
-  for(auto i0 = node.CBegin(), i1 = node.CEnd(); i0 != i1; ++i0)
-  {
-    auto kv = *i0;
-    switch(kv.second.GetType())
-    {
-      case TreeNode::ARRAY:
-      {
-        Property::Array innerArray;
-        ParseProperties(kv.second, innerArray);
-        array.PushBack(innerArray);
-        break;
-      }
-
-      case TreeNode::OBJECT:
-      {
-        Property::Map map;
-        ParseProperties(kv.second, map);
-        array.PushBack(map);
-        break;
-      }
-
-      case TreeNode::STRING:
-      {
-        array.PushBack(kv.second.GetString());
-        break;
-      }
-
-      case TreeNode::INTEGER:
-      {
-        array.PushBack(kv.second.GetInteger());
-        break;
-      }
-
-      case TreeNode::BOOLEAN:
-      {
-        array.PushBack(kv.second.GetBoolean());
-        break;
-      }
-
-      case TreeNode::FLOAT:
-      {
-        array.PushBack(kv.second.GetFloat());
-        break;
-      }
-
-      case TreeNode::IS_NULL:
-      {
-        break;
-      }
-    }
-  }
-}
-
-} // namespace
-
-struct DliLoader::Impl
-{
-  StringCallback      mOnError = DefaultErrorCallback;
-  Toolkit::JsonParser mParser;
-
-  void ParseScene(LoadParams& params);
-
-private:
-  std::map<Index, Matrix> mInverseBindMatrices;
-
-  /**
-   * @brief Due to .dli nodes being processed in depth-first traversal with orphans being
-   *  ignored, features that rely on node indices (which is more compact and closer to
-   *  glTF) require a mapping from .dli node indices to those in the resulting SceneDefinition.
-   *  The index mapper is responsible for maintaing this mapping, and resolving node IDs
-   *  once the processing of the nodes has finished.
-   * @note The resolution requires the whole scene graph to finish parsing, therefore any
-   *  node extensions relying on node IDs will see the dli ID in their processor.
-   */
-  struct IIndexMapper
-  {
-    /**
-     * @brief Attempts to create a mapping from a node's @a dli index to its @a scene
-     *  index.
-     * @return Whether the operation was successful.
-     */
-    virtual bool Map(Index iDli, Index iScene) = 0;
-
-    /**
-     * @return The scene index for the node's @a dli index.
-     */
-    virtual Index Resolve(Index iDli) = 0;
-  };
-
-  /**
-   * @brief Traverses the DOM tree created by LoadDocument() in an attempt to create
-   *  an intermediate representation of resources and nodes.
-   */
-  void ParseSceneInternal(Index iScene, const Toolkit::TreeNode* tnScenes, const Toolkit::TreeNode* tnNodes, LoadParams& params);
-
-  void ParseSkeletons(const Toolkit::TreeNode* skeletons, SceneDefinition& scene, ResourceBundle& resources);
-  void ParseEnvironments(const Toolkit::TreeNode* environments, ResourceBundle& resources);
-  void ParseMaterials(const Toolkit::TreeNode* materials, ConvertColorCode convertColorCode, ResourceBundle& resources);
-
-  void ParseNodes(const Toolkit::TreeNode* nodes, Index index, LoadParams& params);
-  void ParseNodesInternal(const Toolkit::TreeNode* nodes, Index index, std::vector<Index>& inOutParentStack, LoadParams& params, IIndexMapper& indexMapper);
-
-  void ParseAnimations(const Toolkit::TreeNode* animations, LoadParams& params);
-  void ParseAnimationGroups(const Toolkit::TreeNode* animationGroups, LoadParams& params);
-
-  void ParseShaders(const Toolkit::TreeNode* shaders, ResourceBundle& resources);
-  void ParseMeshes(const Toolkit::TreeNode* meshes, ResourceBundle& resources);
-
-  void GetCameraParameters(std::vector<CameraParameters>& cameras) const;
-  void GetLightParameters(std::vector<LightParameters>& lights) const;
-};
-
-DliLoader::DliLoader()
-: mImpl{new Impl}
-{
-}
-
-DliLoader::~DliLoader() = default;
-
-void DliLoader::SetErrorCallback(StringCallback onError)
-{
-  mImpl->mOnError = onError;
-}
-
-bool DliLoader::LoadScene(const std::string& uri, LoadParams& params)
-{
-  std::string daliBuffer = LoadTextFile(uri.c_str());
-
-  auto& parser = mImpl->mParser;
-  parser       = JsonParser::New();
-  if(!parser.Parse(daliBuffer))
-  {
-    return false;
-  }
-
-  mImpl->ParseScene(params);
-  return true;
-}
-
-std::string DliLoader::GetParseError() const
-{
-  std::stringstream stream;
-
-  auto& parser = mImpl->mParser;
-  if(parser.ParseError())
-  {
-    stream << "position: " << parser.GetErrorPosition() << ", line: " << parser.GetErrorLineNumber() << ", column: " << parser.GetErrorColumn() << ", description: " << parser.GetErrorDescription() << ".";
-  }
-
-  return stream.str();
-}
-
-void DliLoader::Impl::ParseScene(LoadParams& params)
-{
-  auto& input  = params.input;
-  auto& output = params.output;
-
-  // get index of root node.
-  auto docRoot = mParser.GetRoot();
-  if(docRoot)
-  {
-    // Process resources first - these are shared
-    if(auto environments = docRoot->GetChild("environment"))
-    {
-      ParseEnvironments(environments, output.mResources); // NOTE: must precede parsing of materials
-    }
-
-    if(auto meshes = docRoot->GetChild("meshes"))
-    {
-      ParseMeshes(meshes, output.mResources);
-    }
-
-    if(auto shaders = docRoot->GetChild("shaders"))
-    {
-      ParseShaders(shaders, output.mResources);
-    }
-
-    if(auto materials = docRoot->GetChild("materials"))
-    {
-      ParseMaterials(materials, input.mConvertColorCode, output.mResources);
-    }
-
-    for(auto& c : input.mPreNodeCategoryProcessors)
-    {
-      if(auto node = docRoot->GetChild(c.first))
-      {
-        Property::Array array;
-        ParseProperties(*node, array);
-        c.second(std::move(array), mOnError);
-      }
-    }
-
-    // Process scenes
-    Index iScene = 0; // default scene
-    ReadIndex(docRoot->GetChild("scene"), iScene);
-
-    auto tnScenes = RequireChild(docRoot, "scenes");
-    auto tnNodes  = RequireChild(docRoot, "nodes");
-    ParseSceneInternal(iScene, tnScenes, tnNodes, params);
-
-    ParseSkeletons(docRoot->GetChild("skeletons"), output.mScene, output.mResources);
-
-    output.mScene.EnsureUniqueSkinningShaderInstances(output.mResources);
-    output.mScene.EnsureUniqueBlendShapeShaderInstances(output.mResources);
-
-    // Ger cameras and lights
-    GetCameraParameters(output.mCameraParameters);
-    GetLightParameters(output.mLightParameters);
-
-    // Post-node processors and animations last
-    for(auto& c : input.mPostNodeCategoryProcessors)
-    {
-      if(auto node = docRoot->GetChild(c.first))
-      {
-        Property::Array array;
-        ParseProperties(*node, array);
-        c.second(std::move(array), mOnError);
-      }
-    }
-
-    if(auto animations = docRoot->GetChild("animations"))
-    {
-      ParseAnimations(animations, params);
-    }
-
-    if(!output.mAnimationDefinitions.empty())
-    {
-      if(auto animationGroups = docRoot->GetChild("animationGroups"))
-      {
-        ParseAnimationGroups(animationGroups, params);
-      }
-    }
-  }
-}
-
-void DliLoader::Impl::ParseSceneInternal(Index iScene, const Toolkit::TreeNode* tnScenes, const Toolkit::TreeNode* tnNodes, LoadParams& params)
-{
-  auto getSceneRootIdx = [tnScenes, tnNodes](Index iScene) {
-    auto tn = GetNthChild(tnScenes, iScene); // now a "scene" object
-    if(!tn)
-    {
-      ExceptionFlinger(ASSERT_LOCATION) << iScene << " is out of bounds access into " << SCENES << ".";
-    }
-
-    tn = RequireChild(tn, NODES); // now a "nodes" array
-    if(tn->GetType() != TreeNode::ARRAY)
-    {
-      ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " has an invalid type; array required.";
-    }
-
-    if(tn->Size() < 1)
-    {
-      ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " must define a node id.";
-    }
-
-    tn = GetNthChild(tn, 0); // now the first element of the array
-    Index iRootNode;
-    if(!ReadIndex(tn, iRootNode))
-    {
-      ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " has an invalid value for root node index: '" << iRootNode << "'.";
-    }
-
-    if(iRootNode >= tnNodes->Size())
-    {
-      ExceptionFlinger(ASSERT_LOCATION) << "Root node index << " << iRootNode << " of scene " << iScene << " is out of bounds.";
-    }
-
-    tn = GetNthChild(tnNodes, iRootNode); // now a "node" object
-    if(tn->GetType() != TreeNode::OBJECT)
-    {
-      ExceptionFlinger(ASSERT_LOCATION) << "Root node of scene " << iScene << " is of invalid JSON type; object required";
-    }
-
-    return iRootNode;
-  };
-
-  Index iRootNode = getSceneRootIdx(iScene);
-  ParseNodes(tnNodes, iRootNode, params);
-
-  auto& scene = params.output.mScene;
-  scene.AddRootNode(0);
-
-  for(Index i = 0; i < iScene; ++i)
-  {
-    Index       iRootNode = getSceneRootIdx(i);
-    const Index iRoot     = scene.GetNodeCount();
-    ParseNodes(tnNodes, iRootNode, params);
-    scene.AddRootNode(iRoot);
-  }
-
-  auto numScenes = tnScenes->Size();
-  for(Index i = iScene + 1; i < numScenes; ++i)
-  {
-    Index       iRootNode = getSceneRootIdx(i);
-    const Index iRoot     = scene.GetNodeCount();
-    ParseNodes(tnNodes, iRootNode, params);
-    scene.AddRootNode(iRoot);
-  }
-}
-
-void DliLoader::Impl::ParseSkeletons(const TreeNode* skeletons, SceneDefinition& scene, ResourceBundle& resources)
-{
-  if(skeletons)
-  {
-    auto iStart = skeletons->CBegin();
-    for(auto i0 = iStart, i1 = skeletons->CEnd(); i0 != i1; ++i0)
-    {
-      auto&       node = (*i0).second;
-      std::string skeletonRootName;
-      if(ReadString(node.GetChild(NODE), skeletonRootName))
-      {
-        SkeletonDefinition skeleton;
-        if(!scene.FindNode(skeletonRootName, &skeleton.mRootNodeIdx))
-        {
-          ExceptionFlinger(ASSERT_LOCATION) << FormatString("Skeleton %d: node '%s' not defined.", resources.mSkeletons.size(), skeletonRootName.c_str());
-        }
-
-        uint32_t                   jointCount = 0;
-        std::function<void(Index)> visitFn;
-        auto&                      ibms = mInverseBindMatrices;
-        visitFn                         = [&](Index id) {
-          auto node = scene.GetNode(id);
-          jointCount += ibms.find(id) != ibms.end();
-
-          for(auto i : node->mChildren)
-          {
-            visitFn(i);
-          }
-        };
-        visitFn(skeleton.mRootNodeIdx);
-
-        if(jointCount > Skinning::MAX_JOINTS)
-        {
-          mOnError(FormatString("Skeleton %d: joint count exceeds supported limit.", resources.mSkeletons.size()));
-          jointCount = Skinning::MAX_JOINTS;
-        }
-
-        skeleton.mJoints.reserve(jointCount);
-
-        visitFn = [&](Index id) {
-          auto iFind = ibms.find(id);
-          if(iFind != ibms.end() && skeleton.mJoints.size() < Skinning::MAX_JOINTS)
-          {
-            skeleton.mJoints.push_back({id, iFind->second});
-          }
-
-          auto node = scene.GetNode(id);
-          for(auto i : node->mChildren)
-          {
-            visitFn(i);
-          }
-        };
-        visitFn(skeleton.mRootNodeIdx);
-
-        resources.mSkeletons.push_back(std::move(skeleton));
-      }
-      else
-      {
-        ExceptionFlinger(ASSERT_LOCATION) << "skeleton " << std::distance(iStart, i0) << ": Missing required attribute '" << NODE << "'.";
-      }
-    }
-  }
-}
-
-void DliLoader::Impl::ParseEnvironments(const TreeNode* environments, ResourceBundle& resources)
-{
-  Matrix cubeOrientation(Matrix::IDENTITY);
-
-  for(auto i0 = environments->CBegin(), i1 = environments->CEnd(); i0 != i1; ++i0)
-  {
-    auto& node = (*i0).second;
-
-    EnvironmentDefinition envDef;
-    ReadString(node.GetChild("cubeSpecular"), envDef.mSpecularMapPath);
-    ReadString(node.GetChild("cubeDiffuse"), envDef.mDiffuseMapPath);
-    ToUnixFileSeparators(envDef.mSpecularMapPath);
-    ToUnixFileSeparators(envDef.mDiffuseMapPath);
-    envDef.mIblIntensity = 1.0f;
-    ReadFloat(node.GetChild("iblIntensity"), envDef.mIblIntensity);
-    if(ReadVector(node.GetChild("cubeInitialOrientation"), cubeOrientation.AsFloat(), 16u))
-    {
-      envDef.mCubeOrientation = Quaternion(cubeOrientation);
-    }
-
-    resources.mEnvironmentMaps.emplace_back(std::move(envDef), EnvironmentDefinition::Textures());
-  }
-
-  // NOTE: guarantees environmentMaps to have an empty environment.
-  if(resources.mEnvironmentMaps.empty())
-  {
-    resources.mEnvironmentMaps.emplace_back(EnvironmentDefinition(), EnvironmentDefinition::Textures());
-  }
-}
-
-void DliLoader::Impl::ParseShaders(const TreeNode* shaders, ResourceBundle& resources)
-{
-  uint32_t iShader = 0;
-  for(auto i0 = shaders->CBegin(), i1 = shaders->CEnd(); i0 != i1; ++i0, ++iShader)
-  {
-    auto&            node = (*i0).second;
-    ShaderDefinition shaderDef;
-    ReadStringVector(node.GetChild("defines"), shaderDef.mDefines);
-
-    // Read shader hints. Possible values are:
-    //                         Don't define for No hints.
-    // "OUTPUT_IS_TRANSPARENT" Might generate transparent alpha from opaque inputs.
-    //     "MODIFIES_GEOMETRY" Might change position of vertices, this option disables any culling optimizations.
-
-    ReadStringVector(node.GetChild(HINTS), shaderDef.mHints);
-
-    if(ReadString(node.GetChild("vertex"), shaderDef.mVertexShaderPath) &&
-       ReadString(node.GetChild("fragment"), shaderDef.mFragmentShaderPath))
-    {
-      ToUnixFileSeparators(shaderDef.mVertexShaderPath);
-      ToUnixFileSeparators(shaderDef.mFragmentShaderPath);
-
-      for(TreeNode::ConstIterator j0 = node.CBegin(), j1 = node.CEnd(); j0 != j1; ++j0)
-      {
-        const TreeNode::KeyNodePair& keyValue = *j0;
-        const std::string&           key      = keyValue.first;
-        const TreeNode&              value    = keyValue.second;
-
-        Property::Value uniformValue;
-        if(key.compare("vertex") == 0 || key.compare("fragment") == 0 || key.compare("defines") == 0 || key.compare(HINTS) == 0)
-        {
-          continue;
-        }
-        else if(key.compare("rendererState") == 0)
-        {
-          shaderDef.mRendererState = ReadRendererState(keyValue.second);
-        }
-        else if(value.GetType() == TreeNode::INTEGER || value.GetType() == TreeNode::FLOAT)
-        {
-          float f = 0.f;
-          ReadFloat(&value, f);
-          uniformValue = f;
-        }
-        else if(value.GetType() == TreeNode::BOOLEAN)
-        {
-          DALI_LOG_WARNING("\"bool\" uniforms are handled as floats in shader");
-          bool value = false;
-          if(ReadBool(&keyValue.second, value))
-          {
-            uniformValue = value ? 1.0f : 0.0f;
-          }
-        }
-        else
-          switch(auto size = GetNumericalArraySize(&value))
-          {
-            case 16:
-            {
-              Matrix m;
-              ReadVector(&value, m.AsFloat(), size);
-              uniformValue = m;
-              break;
-            }
-
-            case 9:
-            {
-              Matrix3 m;
-              ReadVector(&value, m.AsFloat(), size);
-              uniformValue = m;
-              break;
-            }
-
-            case 4:
-            {
-              Vector4 v;
-              ReadVector(&value, v.AsFloat(), size);
-              uniformValue = v;
-              break;
-            }
-
-            case 3:
-            {
-              Vector3 v;
-              ReadVector(&value, v.AsFloat(), size);
-              uniformValue = v;
-              break;
-            }
-
-            case 2:
-            {
-              Vector2 v;
-              ReadVector(&value, v.AsFloat(), size);
-              uniformValue = v;
-              break;
-            }
-
-            default:
-              mOnError(FormatString(
-                "shader %u: Ignoring uniform '%s': failed to infer type from %zu elements.",
-                iShader,
-                key.c_str(),
-                size));
-              break;
-          }
-
-        if(Property::NONE != uniformValue.GetType())
-        {
-          shaderDef.mUniforms.Insert(key, uniformValue);
-        }
-      }
-
-      resources.mShaders.emplace_back(std::move(shaderDef), Shader());
-    }
-    else
-    {
-      ExceptionFlinger(ASSERT_LOCATION) << "shader " << iShader << ": Missing vertex / fragment shader definition.";
-    }
-  }
-}
-
-void DliLoader::Impl::ParseMeshes(const TreeNode* meshes, ResourceBundle& resources)
-{
-  for(auto i0 = meshes->CBegin(), i1 = meshes->CEnd(); i0 != i1; ++i0)
-  {
-    auto& node = (*i0).second;
-
-    MeshDefinition meshDef;
-    if(!ReadString(node.GetChild(URI), meshDef.mUri))
-    {
-      ExceptionFlinger(ASSERT_LOCATION) << "mesh " << resources.mMeshes.size() << ": Missing required attribute '" << URI << "'.";
-    }
-
-    ToUnixFileSeparators(meshDef.mUri);
-
-    std::string primitive;
-    if(ReadString(node.GetChild("primitive"), primitive))
-    {
-      if(primitive == "LINES")
-      {
-        meshDef.mPrimitiveType = Geometry::LINES;
-      }
-      else if(primitive == "POINTS")
-      {
-        meshDef.mPrimitiveType = Geometry::POINTS;
-      }
-      else if(primitive != "TRIANGLES")
-      {
-        mOnError(FormatString(
-          "mesh %d: Using TRIANGLES instead of unsupported primitive type '%s'.",
-          resources.mMeshes.size(),
-          primitive.c_str()));
-      }
-    }
-
-    int attributes;
-    if(ReadInt(node.GetChild("attributes"), attributes))
-    {
-      if(MaskMatch(attributes, MeshDefinition::INDICES) &&
-         !ReadAttribAccessor(node.GetChild("indices"), meshDef.mIndices))
-      {
-        ExceptionFlinger(ASSERT_LOCATION) << FormatString("mesh %d: Failed to read %s.",
-                                                          resources.mMeshes.size(),
-                                                          "indices");
-      }
-
-      if(MaskMatch(attributes, MeshDefinition::POSITIONS) &&
-         !ReadAttribAccessor(node.GetChild("positions"), meshDef.mPositions))
-      {
-        ExceptionFlinger(ASSERT_LOCATION) << FormatString("mesh %d: Failed to read %s.",
-                                                          resources.mMeshes.size(),
-                                                          "positions");
-      }
-
-      if(MaskMatch(attributes, MeshDefinition::NORMALS) &&
-         !ReadAttribAccessor(node.GetChild("normals"), meshDef.mNormals))
-      {
-        mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "normals"));
-      }
-
-      if(MaskMatch(attributes, MeshDefinition::TEX_COORDS) &&
-         !ReadAttribAccessor(node.GetChild("textures"), meshDef.mTexCoords))
-      {
-        mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "textures"));
-      }
-
-      if(MaskMatch(attributes, MeshDefinition::TANGENTS) &&
-         !ReadAttribAccessor(node.GetChild("tangents"), meshDef.mTangents))
-      {
-        mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "tangents"));
-      }
-
-      // NOTE: we're no longer reading bitangents as these are calculated in the shaders.
-      if(ReadIndex(node.GetChild("skeleton"), meshDef.mSkeletonIdx))
-      {
-        if(!MaskMatch(attributes, MeshDefinition::JOINTS_0) &&
-           !MaskMatch(attributes, MeshDefinition::WEIGHTS_0))
-        {
-          mOnError(FormatString("mesh %d: Expected joints0 / weights0 attribute(s) missing.",
-                                resources.mMeshes.size()));
-        }
-        else if(!ReadAttribAccessor(node.GetChild("joints0"), meshDef.mJoints0) ||
-                !ReadAttribAccessor(node.GetChild("weights0"), meshDef.mWeights0))
-        {
-          mOnError(FormatString("mesh %d: Failed to read skinning information.",
-                                resources.mMeshes.size()));
-        }
-      }
-
-      if(auto blendshapeHeader = node.GetChild(BLEND_SHAPE_HEADER))
-      {
-        std::string blendShapeVersion;
-        ReadString(blendshapeHeader->GetChild(VERSION), blendShapeVersion);
-
-        if(0u == blendShapeVersion.compare(BLEND_SHAPE_VERSION_1_0))
-        {
-          meshDef.mBlendShapeVersion = BlendShapes::Version::VERSION_1_0;
-        }
-        else if(0u == blendShapeVersion.compare(BLEND_SHAPE_VERSION_2_0))
-        {
-          meshDef.mBlendShapeVersion = BlendShapes::Version::VERSION_2_0;
-        }
-
-        switch(meshDef.mBlendShapeVersion)
-        {
-          case BlendShapes::Version::VERSION_1_0:
-          case BlendShapes::Version::VERSION_2_0: // FALL THROUGH
-          {
-            ReadAttribBlob(blendshapeHeader, meshDef.mBlendShapeHeader);
-            break;
-          }
-          default:
-          {
-            // nothing to do
-            break;
-          }
-        }
-      }
-
-      if(auto blendShapes = node.GetChild(BLEND_SHAPES))
-      {
-        meshDef.mBlendShapes.resize(blendShapes->Size());
-
-        auto index = 0u;
-        for(auto it = blendShapes->CBegin(), endIt = blendShapes->CEnd(); it != endIt; ++it, ++index)
-        {
-          // Each blend shape is stored as the difference with the original mesh.
-
-          auto& blendShapeNode = (*it).second;
-
-          auto& blendShape = meshDef.mBlendShapes[index];
-          ReadString(blendShapeNode.GetChild("name"), blendShape.name);
-          if(auto position = blendShapeNode.GetChild("positions"))
-          {
-            ReadAttribAccessor(position, blendShape.deltas);
-          }
-          if(auto normals = blendShapeNode.GetChild("normals"))
-          {
-            ReadAttribAccessor(normals, blendShape.normals);
-          }
-          if(auto tangents = blendShapeNode.GetChild("tangents"))
-          {
-            ReadAttribAccessor(tangents, blendShape.tangents);
-          }
-          ReadFloat(blendShapeNode.GetChild("weight"), blendShape.weight);
-        }
-      }
-
-      bool flipV;
-      if(ReadBool(node.GetChild("flipV"), flipV))
-      {
-        meshDef.mFlags |= flipV * MeshDefinition::FLIP_UVS_VERTICAL;
-      }
-
-      resources.mMeshes.emplace_back(std::move(meshDef), MeshGeometry());
-    }
-  }
-}
-
-void DliLoader::Impl::ParseMaterials(const TreeNode* materials, ConvertColorCode convertColorCode, ResourceBundle& resources)
-{
-  for(auto i0 = materials->CBegin(), i1 = materials->CEnd(); i0 != i1; ++i0)
-  {
-    auto& node = (*i0).second;
-
-    MaterialDefinition materialDef;
-    if(auto eEnvironment = node.GetChild("environment"))
-    {
-      ReadIndex(eEnvironment, materialDef.mEnvironmentIdx);
-      if(static_cast<unsigned int>(materialDef.mEnvironmentIdx) >= resources.mEnvironmentMaps.size())
-      {
-        ExceptionFlinger(ASSERT_LOCATION) << "material " << resources.mMaterials.size() << ": Environment index " << materialDef.mEnvironmentIdx << " out of bounds (" << resources.mEnvironmentMaps.size() << ").";
-      }
-    }
-
-    // TODO : need to consider AGIF
-    std::vector<std::string> texturePaths;
-    std::string              texturePath;
-    if(ReadString(node.GetChild("albedoMap"), texturePath))
-    {
-      ToUnixFileSeparators(texturePath);
-      const auto semantic = MaterialDefinition::ALBEDO;
-      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
-      materialDef.mFlags |= semantic | MaterialDefinition::TRANSPARENCY; // NOTE: only in dli does single / separate ALBEDO texture mean TRANSPARENCY.
-    }
-    if(ReadString(node.GetChild("albedoMetallicMap"), texturePath))
-    {
-      ToUnixFileSeparators(texturePath);
-
-      if(MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO))
-      {
-        mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "albedo"));
-      }
-
-      const auto semantic = MaterialDefinition::ALBEDO | MaterialDefinition::METALLIC;
-      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
-      materialDef.mFlags |= semantic;
-    }
-
-    if(ReadString(node.GetChild("metallicRoughnessMap"), texturePath))
-    {
-      ToUnixFileSeparators(texturePath);
-
-      if(MaskMatch(materialDef.mFlags, MaterialDefinition::METALLIC))
-      {
-        mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "metallic"));
-      }
-
-      const auto semantic = MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS;
-      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
-      materialDef.mFlags |= semantic |
-                            // We have a metallic-roughhness map and the first texture did not have albedo semantics - we're in the transparency workflow.
-                            (MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO) * MaterialDefinition::TRANSPARENCY);
-    }
-
-    if(ReadString(node.GetChild("normalMap"), texturePath))
-    {
-      ToUnixFileSeparators(texturePath);
-
-      const auto semantic = MaterialDefinition::NORMAL;
-      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
-      materialDef.mFlags |= semantic |
-                            // We have a standalone normal map and the first texture did not have albedo semantics - we're in the transparency workflow.
-                            (MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO) * MaterialDefinition::TRANSPARENCY);
-    }
-
-    if(ReadString(node.GetChild("normalRoughnessMap"), texturePath))
-    {
-      ToUnixFileSeparators(texturePath);
-
-      if(MaskMatch(materialDef.mFlags, MaterialDefinition::NORMAL))
-      {
-        mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "normal"));
-      }
-
-      if(MaskMatch(materialDef.mFlags, MaterialDefinition::ROUGHNESS))
-      {
-        mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "roughness"));
-      }
-
-      if(MaskMatch(materialDef.mFlags, MaterialDefinition::TRANSPARENCY))
-      {
-        mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "transparency"));
-      }
-
-      const auto semantic = MaterialDefinition::NORMAL | MaterialDefinition::ROUGHNESS;
-      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
-      materialDef.mFlags |= semantic;
-    }
-
-    if(ReadString(node.GetChild("subsurfaceMap"), texturePath))
-    {
-      ToUnixFileSeparators(texturePath);
-
-      const auto semantic = MaterialDefinition::SUBSURFACE;
-      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
-      materialDef.mFlags |= semantic;
-    }
-
-    if(ReadString(node.GetChild("occlusionMap"), texturePath))
-    {
-      ToUnixFileSeparators(texturePath);
-      const auto semantic = MaterialDefinition::OCCLUSION;
-      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
-      materialDef.mFlags |= semantic;
-    }
-
-    if(ReadColorCodeOrColor(&node, materialDef.mColor, convertColorCode) &&
-       materialDef.mColor.a < 1.0f)
-    {
-      materialDef.mFlags |= MaterialDefinition::TRANSPARENCY;
-    }
-
-    ReadFloat(node.GetChild("metallic"), materialDef.mMetallic);
-    ReadFloat(node.GetChild("roughness"), materialDef.mRoughness);
-
-    bool mipmaps;
-    if(ReadBool(node.GetChild("mipmap"), mipmaps) && mipmaps)
-    {
-      for(auto& ts : materialDef.mTextureStages)
-      {
-        ts.mTexture.mSamplerFlags |= SamplerFlags::FILTER_MIPMAP_LINEAR;
-      }
-    }
-
-    resources.mMaterials.emplace_back(std::move(materialDef), TextureSet());
-  }
-}
-
-void DliLoader::Impl::ParseNodes(const TreeNode* const nodes, Index index, LoadParams& params)
-{
-  std::vector<Index> parents;
-  parents.reserve(8);
-
-  struct IndexMapper : IIndexMapper
-  {
-    IndexMapper(size_t numNodes)
-    {
-      mIndices.reserve(numNodes);
-    }
-
-    virtual bool Map(Index iDli, Index iScene) override
-    {
-      Entry idx{iDli, iScene};
-      auto  iInsert = std::lower_bound(mIndices.begin(), mIndices.end(), idx);
-      if(iInsert == mIndices.end() || iInsert->iDli != iDli)
-      {
-        mIndices.insert(iInsert, idx);
-      }
-      else if(iInsert->iScene != iScene)
-      {
-        return false;
-      }
-      return true;
-    }
-
-    virtual unsigned int Resolve(Index iDli) override
-    {
-      auto iFind = std::lower_bound(mIndices.begin(), mIndices.end(), iDli, [](const Entry& idx, Index iDli) { return idx.iDli < iDli; });
-      DALI_ASSERT_ALWAYS(iFind != mIndices.end());
-      return iFind->iScene;
-    }
-
-  private:
-    struct Entry
-    {
-      unsigned int iDli;
-      unsigned int iScene;
-
-      bool operator<(const Entry& other) const
-      {
-        return iDli < other.iDli;
-      }
-    };
-    std::vector<Entry> mIndices;
-  } mapper(nodes->Size());
-  ParseNodesInternal(nodes, index, parents, params, mapper);
-
-  auto& scene = params.output.mScene;
-  for(size_t i0 = 0, i1 = scene.GetNodeCount(); i0 < i1; ++i0)
-  {
-    for(auto& c : scene.GetNode(i0)->mConstraints)
-    {
-      c.mSourceIdx = mapper.Resolve(c.mSourceIdx);
-    }
-  }
-}
-
-void DliLoader::Impl::ParseNodesInternal(const TreeNode* const nodes, Index index, std::vector<Index>& inOutParentStack, LoadParams& params, IIndexMapper& mapper)
-{
-  // Properties that may be resolved from a JSON value with ReadInt() -- or default to 0.
-  struct IndexProperty
-  {
-    ResourceType::Value type;
-    const TreeNode*     source;
-    Index&              target;
-  };
-  std::vector<IndexProperty> resourceIds;
-  resourceIds.reserve(4);
-
-  if(auto node = GetNthChild(nodes, index))
-  {
-    NodeDefinition nodeDef;
-    nodeDef.mParentIdx = inOutParentStack.empty() ? INVALID_INDEX : inOutParentStack.back();
-
-    // name
-    ReadString(node->GetChild(NAME), nodeDef.mName);
-
-    // transform
-    ReadModelTransform(node, nodeDef.mOrientation, nodeDef.mPosition, nodeDef.mScale);
-
-    // Reads the size of the node.
-    //
-    // * It can be given as 'size' or 'bounds'.
-    // * The sdk saves the 'size' as a vector2 in some cases.
-    // * To avoid size related issues the following code attemps
-    //   to read the 'size/bounds' as a vector3 first, if it's
-    //   not successful then reads it as a vector2.
-    ReadVector(node->GetChild("size"), nodeDef.mSize.AsFloat(), 3) ||
-      ReadVector(node->GetChild("size"), nodeDef.mSize.AsFloat(), 2) ||
-      ReadVector(node->GetChild("bounds"), nodeDef.mSize.AsFloat(), 3) ||
-      ReadVector(node->GetChild("bounds"), nodeDef.mSize.AsFloat(), 2);
-
-    // visibility
-    ReadBool(node->GetChild("visible"), nodeDef.mIsVisible);
-
-    // type classification
-    if(auto eCustomization = node->GetChild("customization")) // customization
-    {
-      std::string tag;
-      if(ReadString(eCustomization->GetChild("tag"), tag))
-      {
-        nodeDef.mCustomization.reset(new NodeDefinition::CustomizationDefinition{tag});
-      }
-    }
-    else // something renderable maybe
-    {
-      std::unique_ptr<NodeDefinition::Renderable> renderable;
-      ModelRenderable*                            modelRenderable = nullptr; // no ownership, aliasing renderable for the right type.
-
-      const TreeNode* eRenderable = nullptr;
-      if((eRenderable = node->GetChild("model")))
-      {
-        // check for mesh before allocating - this can't be missing.
-        auto eMesh = eRenderable->GetChild("mesh");
-        if(!eMesh)
-        {
-          ExceptionFlinger(ASSERT_LOCATION) << "node " << nodeDef.mName << ": Missing mesh definition.";
-        }
-
-        modelRenderable = new ModelRenderable();
-        renderable.reset(modelRenderable);
-
-        resourceIds.push_back({ResourceType::Mesh, eMesh, modelRenderable->mMeshIdx});
-      }
-      else if((eRenderable = node->GetChild("arc")))
-      {
-        // check for mesh before allocating - this can't be missing.
-        auto eMesh = eRenderable->GetChild("mesh");
-        if(!eMesh)
-        {
-          ExceptionFlinger(ASSERT_LOCATION) << "node " << nodeDef.mName << ": Missing mesh definition.";
-        }
-
-        auto arcRenderable = new ArcRenderable;
-        renderable.reset(arcRenderable);
-        modelRenderable = arcRenderable;
-
-        resourceIds.push_back({ResourceType::Mesh, eMesh, arcRenderable->mMeshIdx});
-
-        ReadArcField(eRenderable, *arcRenderable);
-      }
-
-      if(renderable && eRenderable != nullptr) // process common properties of all renderables + register payload
-      {
-        // shader
-        renderable->mShaderIdx = 0;
-        auto eShader           = eRenderable->GetChild("shader");
-        if(eShader)
-        {
-          resourceIds.push_back({ResourceType::Shader, eShader, renderable->mShaderIdx});
-        }
-
-        // color
-        if(modelRenderable)
-        {
-          modelRenderable->mMaterialIdx = 0; // must offer default of 0
-          auto eMaterial                = eRenderable->GetChild("material");
-          if(eMaterial)
-          {
-            resourceIds.push_back({ResourceType::Material, eMaterial, modelRenderable->mMaterialIdx});
-          }
-
-          if(!ReadColorCodeOrColor(eRenderable, modelRenderable->mColor, params.input.mConvertColorCode))
-          {
-            ReadColorCodeOrColor(node, modelRenderable->mColor, params.input.mConvertColorCode);
-          }
-        }
-
-        nodeDef.mRenderables.push_back(std::move(renderable));
-      }
-    }
-
-    // Resolve ints - default to 0 if undefined
-    auto& output = params.output;
-    for(auto& idRes : resourceIds)
-    {
-      Index iCheck = 0;
-      switch(idRes.type)
-      {
-        case ResourceType::Shader:
-          iCheck = output.mResources.mShaders.size();
-          break;
-
-        case ResourceType::Mesh:
-          iCheck = output.mResources.mMeshes.size();
-          break;
-
-        case ResourceType::Material:
-          iCheck = output.mResources.mMaterials.size();
-          break;
-
-        default:
-          ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": Invalid resource type: " << idRes.type << " (Programmer error)";
-      }
-
-      if(!idRes.source)
-      {
-        idRes.target = 0;
-      }
-      else if(idRes.source->GetType() != TreeNode::INTEGER)
-      {
-        ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": Invalid " << GetResourceTypeName(idRes.type) << " index type.";
-      }
-      else
-      {
-        idRes.target = idRes.source->GetInteger();
-      }
-
-      if(idRes.target >= iCheck)
-      {
-        ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": " << GetResourceTypeName(idRes.type) << " index " << idRes.target << " out of bounds (" << iCheck << ").";
-      }
-    }
-    resourceIds.clear();
-
-    // Extra properties
-    if(auto eExtras = node->GetChild("extras"))
-    {
-      auto& extras = nodeDef.mExtras;
-      extras.reserve(eExtras->Size());
-
-      for(auto i0 = eExtras->CBegin(), i1 = eExtras->CEnd(); i0 != i1; ++i0)
-      {
-        NodeDefinition::Extra e;
-
-        auto eExtra = *i0;
-        e.mKey      = eExtra.first;
-        if(e.mKey.empty())
-        {
-          mOnError(FormatString("node %d: empty string is invalid for name of extra %d; ignored.",
-                                index,
-                                extras.size()));
-          continue;
-        }
-
-        e.mValue = ReadPropertyValue(eExtra.second);
-        if(e.mValue.GetType() == Property::Type::NONE)
-        {
-          mOnError(FormatString("node %d: failed to interpret value of extra '%s' : %s; ignored.",
-                                index,
-                                e.mKey.c_str(),
-                                eExtra.second.GetString()));
-        }
-        else
-        {
-          auto iInsert = std::lower_bound(extras.begin(), extras.end(), e);
-          if(iInsert != extras.end() && iInsert->mKey == e.mKey)
-          {
-            mOnError(FormatString("node %d: extra '%s' already defined; overriding with %s.",
-                                  index,
-                                  e.mKey.c_str(),
-                                  eExtra.second.GetString()));
-            *iInsert = std::move(e);
-          }
-          else
-          {
-            extras.insert(iInsert, e);
-          }
-        }
-      }
-    }
-
-    // Constraints
-    if(auto eConstraints = node->GetChild("constraints"))
-    {
-      auto& constraints = nodeDef.mConstraints;
-      constraints.reserve(eConstraints->Size());
-
-      ConstraintDefinition cDef;
-      for(auto i0 = eConstraints->CBegin(), i1 = eConstraints->CEnd(); i0 != i1; ++i0)
-      {
-        auto eConstraint = *i0;
-        if(!ReadIndex(&eConstraint.second, cDef.mSourceIdx))
-        {
-          mOnError(FormatString("node %d: node ID %s for constraint %d is invalid; ignored.",
-                                index,
-                                eConstraint.second.GetString(),
-                                constraints.size()));
-        }
-        else
-        {
-          cDef.mProperty = eConstraint.first;
-
-          auto iInsert = std::lower_bound(constraints.begin(), constraints.end(), cDef);
-          if(iInsert != constraints.end() && *iInsert == cDef)
-          {
-            mOnError(FormatString("node %d: constraint %s@%d already defined; ignoring.",
-                                  index,
-                                  cDef.mProperty.c_str(),
-                                  cDef.mSourceIdx));
-          }
-          else
-          {
-            constraints.insert(iInsert, cDef);
-          }
-        }
-      }
-    }
-
-    // Determine index for mapping
-    const unsigned int myIndex = output.mScene.GetNodeCount();
-    if(!mapper.Map(index, myIndex))
-    {
-      mOnError(FormatString("node %d: error mapping dli index %d: node has multiple parents. Ignoring subtree.", index, myIndex));
-      return;
-    }
-
-    // if the node is a bone in a skeletal animation, it will have the inverse bind pose matrix.
-    Matrix invBindMatrix{false};
-    if(ReadVector(node->GetChild("inverseBindPoseMatrix"), invBindMatrix.AsFloat(), 16u)) // TODO: more robust error checking?
-    {
-      mInverseBindMatrices[myIndex] = invBindMatrix;
-    }
-
-    // Register nodeDef
-    auto rawDef = output.mScene.AddNode(std::make_unique<NodeDefinition>(std::move(nodeDef)));
-    if(rawDef) // NOTE: no ownership. Guaranteed to stay in scope.
-    {
-      // ...And only then parse children.
-      if(auto children = node->GetChild("children"))
-      {
-        inOutParentStack.push_back(myIndex);
-
-        rawDef->mChildren.reserve(children->Size());
-
-        uint32_t iChild = 0;
-        for(auto j0 = children->CBegin(), j1 = children->CEnd(); j0 != j1; ++j0, ++iChild)
-        {
-          auto& child = (*j0).second;
-          if(child.GetType() == TreeNode::INTEGER)
-          {
-            ParseNodesInternal(nodes, child.GetInteger(), inOutParentStack, params, mapper); // child object is created in scene definition.
-          }
-          else
-          {
-            ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ", child " << iChild << ": invalid index type.";
-          }
-        }
-
-        inOutParentStack.pop_back();
-      }
-      else if(rawDef->mCustomization)
-      {
-        mOnError(FormatString("node %d: not an actual customization without children.", index));
-      }
-
-      if(auto proc = params.input.mNodePropertyProcessor) // optional processing
-      {
-        // WARNING: constraint IDs are not resolved at this point.
-        Property::Map nodeData;
-        ParseProperties(*node, nodeData);
-        proc(*rawDef, std::move(nodeData), mOnError);
-      }
-    }
-    else
-    {
-      ExceptionFlinger(ASSERT_LOCATION) << "Node " << index << ": name already used.";
-    }
-  }
-}
-
-void DliLoader::Impl::ParseAnimations(const TreeNode* tnAnimations, LoadParams& params)
-{
-  auto& definitions = params.output.mAnimationDefinitions;
-  definitions.reserve(definitions.size() + tnAnimations->Size());
-
-  for(TreeNode::ConstIterator iAnim = tnAnimations->CBegin(), iAnimEnd = tnAnimations->CEnd();
-      iAnim != iAnimEnd;
-      ++iAnim)
-  {
-    const TreeNode&     tnAnim = (*iAnim).second;
-    AnimationDefinition animDef;
-    ReadString(tnAnim.GetChild(NAME), animDef.mName);
-
-    auto       iFind     = std::lower_bound(definitions.begin(), definitions.end(), animDef, [](const AnimationDefinition& ad0, const AnimationDefinition& ad1) { return ad0.mName < ad1.mName; });
-    const bool overwrite = iFind != definitions.end() && iFind->mName == animDef.mName;
-    if(overwrite)
-    {
-      mOnError(FormatString("Pre-existing animation with name '%s' is being overwritten.", animDef.mName.c_str()));
-    }
-
-    // Duration -- We need something that animated properties' delay / duration can
-    // be expressed as a multiple of; 0 won't work. This is small enough (i.e. shorter
-    // than our frame delay) to not be restrictive WRT replaying. If anything needs
-    // to occur more frequently, then Animations are likely not your solution anyway.
-    animDef.mDuration = AnimationDefinition::MIN_DURATION_SECONDS;
-    if(!ReadFloat(tnAnim.GetChild("duration"), animDef.mDuration))
-    {
-      mOnError(FormatString("Animation '%s' fails to define '%s', defaulting to %f.",
-                            animDef.mName.c_str(),
-                            "duration",
-                            animDef.mDuration));
-    }
-
-    // Get loop count - # of playbacks. Default is once. 0 means repeat indefinitely.
-    animDef.mLoopCount = 1;
-    if(ReadInt(tnAnim.GetChild("loopCount"), animDef.mLoopCount) &&
-       animDef.mLoopCount < 0)
-    {
-      animDef.mLoopCount = 0;
-    }
-
-    std::string endAction;
-    if(ReadString(tnAnim.GetChild("endAction"), endAction))
-    {
-      if("BAKE" == endAction)
-      {
-        animDef.mEndAction = Animation::BAKE;
-      }
-      else if("DISCARD" == endAction)
-      {
-        animDef.mEndAction = Animation::DISCARD;
-      }
-      else if("BAKE_FINAL" == endAction)
-      {
-        animDef.mEndAction = Animation::BAKE_FINAL;
-      }
-    }
-
-    if(ReadString(tnAnim.GetChild("disconnectAction"), endAction))
-    {
-      if("BAKE" == endAction)
-      {
-        animDef.mDisconnectAction = Animation::BAKE;
-      }
-      else if("DISCARD" == endAction)
-      {
-        animDef.mDisconnectAction = Animation::DISCARD;
-      }
-      else if("BAKE_FINAL" == endAction)
-      {
-        animDef.mDisconnectAction = Animation::BAKE_FINAL;
-      }
-    }
-
-    if(const TreeNode* tnProperties = tnAnim.GetChild("properties"))
-    {
-      animDef.mProperties.reserve(tnProperties->Size());
-      for(TreeNode::ConstIterator iProperty = tnProperties->CBegin(), iPropertyEnd = tnProperties->CEnd();
-          iProperty != iPropertyEnd;
-          ++iProperty)
-      {
-        const TreeNode& tnProperty = (*iProperty).second;
-
-        AnimatedProperty animProp;
-        if(!ReadString(tnProperty.GetChild("node"), animProp.mNodeName))
-        {
-          mOnError(FormatString("Animation '%s': Failed to read the 'node' tag.", animDef.mName.c_str()));
-          continue;
-        }
-
-        if(!ReadString(tnProperty.GetChild("property"), animProp.mPropertyName))
-        {
-          mOnError(FormatString("Animation '%s': Failed to read the 'property' tag", animDef.mName.c_str()));
-          continue;
-        }
-
-        // these are the defaults
-        animProp.mTimePeriod.delaySeconds    = 0.f;
-        animProp.mTimePeriod.durationSeconds = animDef.mDuration;
-        if(!ReadTimePeriod(tnProperty.GetChild("timePeriod"), animProp.mTimePeriod))
-        {
-          mOnError(FormatString("Animation '%s': timePeriod missing in Property #%d: defaulting to %f.",
-                                animDef.mName.c_str(),
-                                animDef.mProperties.size(),
-                                animProp.mTimePeriod.durationSeconds));
-        }
-
-        std::string alphaFunctionValue;
-        if(ReadString(tnProperty.GetChild("alphaFunction"), alphaFunctionValue))
-        {
-          animProp.mAlphaFunction = GetAlphaFunction(alphaFunctionValue);
-        }
-
-        if(const TreeNode* tnKeyFramesBin = tnProperty.GetChild("keyFramesBin"))
-        {
-          DALI_ASSERT_ALWAYS(!animProp.mPropertyName.empty() && "Animation must specify a property name");
-
-          std::ifstream binAniFile;
-          std::string   animationFilename;
-          if(ReadString(tnKeyFramesBin->GetChild(URL), animationFilename))
-          {
-            std::string animationFullPath = params.input.mAnimationsPath + animationFilename;
-            binAniFile.open(animationFullPath, std::ios::binary);
-            if(binAniFile.fail())
-            {
-              ExceptionFlinger(ASSERT_LOCATION) << "Failed to open animation data '" << animationFullPath << "'";
-            }
-          }
-
-          int byteOffset = 0;
-          ReadInt(tnKeyFramesBin->GetChild("byteOffset"), byteOffset);
-          DALI_ASSERT_ALWAYS(byteOffset >= 0);
-
-          binAniFile.seekg(byteOffset, std::ios::beg);
-
-          int numKeys = 0;
-          ReadInt(tnKeyFramesBin->GetChild("numKeys"), numKeys);
-          DALI_ASSERT_ALWAYS(numKeys >= 0);
-
-          animProp.mKeyFrames = KeyFrames::New();
-
-          // In binary animation file only is saved the position, rotation, scale and blend shape weight keys.
-          // so, if it is vector3 we assume is position or scale keys, if it is vector4 we assume is rotation,
-          //  otherwise are blend shape weight keys.
-          //  TODO support for binary header with size information
-          Property::Type propType = Property::FLOAT; // assume blend shape weights
-          if(animProp.mPropertyName == "orientation")
-          {
-            propType = Property::VECTOR4;
-          }
-          else if((animProp.mPropertyName == "position") || (animProp.mPropertyName == "scale"))
-          {
-            propType = Property::VECTOR3;
-          }
-
-          // alphafunction is reserved for future implementation
-          //  NOTE: right now we're just using AlphaFunction::LINEAR.
-          unsigned char dummyAlphaFunction;
-
-          float           progress;
-          Property::Value propValue;
-          for(int key = 0; key < numKeys; key++)
-          {
-            binAniFile.read(reinterpret_cast<char*>(&progress), sizeof(float));
-            if(propType == Property::VECTOR3)
-            {
-              Vector3 value;
-              binAniFile.read(reinterpret_cast<char*>(value.AsFloat()), sizeof(float) * 3);
-              propValue = Property::Value(value);
-            }
-            else if(propType == Property::VECTOR4)
-            {
-              Vector4 value;
-              binAniFile.read(reinterpret_cast<char*>(value.AsFloat()), sizeof(float) * 4);
-              propValue = Property::Value(Quaternion(value));
-            }
-            else
-            {
-              float value;
-              binAniFile.read(reinterpret_cast<char*>(&value), sizeof(float));
-              propValue = Property::Value(value);
-            }
-
-            binAniFile.read(reinterpret_cast<char*>(&dummyAlphaFunction), sizeof(unsigned char));
-
-            animProp.mKeyFrames.Add(progress, propValue, AlphaFunction::LINEAR);
-          }
-        }
-        else if(const TreeNode* tnKeyFrames = tnProperty.GetChild("keyFrames"))
-        {
-          DALI_ASSERT_ALWAYS(!animProp.mPropertyName.empty() && "Animation must specify a property name");
-          animProp.mKeyFrames = KeyFrames::New();
-
-          float progress = 0.0f;
-          for(auto i0 = tnKeyFrames->CBegin(), i1 = tnKeyFrames->CEnd(); i1 != i0; ++i0)
-          {
-            const TreeNode::KeyNodePair& kfKeyChild = *i0;
-            bool                         readResult = ReadFloat(kfKeyChild.second.GetChild("progress"), progress);
-            DALI_ASSERT_ALWAYS(readResult && "Key frame entry must have 'progress'");
-
-            const TreeNode* tnValue = kfKeyChild.second.GetChild("value");
-            DALI_ASSERT_ALWAYS(tnValue && "Key frame entry must have 'value'");
-
-            // For the "orientation" property, convert from Vector4 -> Rotation value
-            // This work-around is preferable to a null-pointer exception in the DALi update thread
-            Property::Value propValue(ReadPropertyValue(*tnValue));
-            if(propValue.GetType() == Property::VECTOR4 &&
-               animProp.mPropertyName == "orientation")
-            {
-              Vector4 v;
-              propValue.Get(v);
-              propValue = Property::Value(Quaternion(v.w, v.x, v.y, v.z));
-            }
-
-            AlphaFunction kfAlphaFunction(AlphaFunction::DEFAULT);
-            std::string   alphaFuncStr;
-            if(ReadString(kfKeyChild.second.GetChild("alphaFunction"), alphaFuncStr))
-            {
-              kfAlphaFunction = GetAlphaFunction(alphaFuncStr);
-            }
-
-            animProp.mKeyFrames.Add(progress, propValue, kfAlphaFunction);
-          }
-        }
-        else
-        {
-          const TreeNode* tnValue = tnProperty.GetChild("value");
-          if(tnValue)
-          {
-            animProp.mValue.reset(new AnimatedProperty::Value{ReadPropertyValue(*tnValue)});
-            ReadBool(tnProperty.GetChild("relative"), animProp.mValue->mIsRelative);
-          }
-          else
-          {
-            mOnError(FormatString("Property '%s' fails to define target value.",
-                                  animProp.mPropertyName.c_str()));
-          }
-        }
-
-        animDef.mProperties.push_back(std::move(animProp));
-      }
-    }
-
-    if(overwrite)
-    {
-      *iFind = std::move(animDef);
-    }
-    else
-    {
-      iFind = definitions.insert(iFind, std::move(animDef));
-    }
-
-    if(auto proc = params.input.mAnimationPropertyProcessor) // optional processing
-    {
-      Property::Map map;
-      ParseProperties(tnAnim, map);
-      proc(animDef, std::move(map), mOnError);
-    }
-  }
-}
-
-void DliLoader::Impl::ParseAnimationGroups(const Toolkit::TreeNode* tnAnimationGroups, LoadParams& params)
-{
-  auto& animGroups = params.output.mAnimationGroupDefinitions;
-
-  int numGroups = 0;
-  for(auto iGroups = tnAnimationGroups->CBegin(), iGroupsEnd = tnAnimationGroups->CEnd();
-      iGroups != iGroupsEnd;
-      ++iGroups, ++numGroups)
-  {
-    const auto& tnGroup = *iGroups;
-    auto        tnName  = tnGroup.second.GetChild(NAME);
-    std::string groupName;
-    if(!tnName || !ReadString(tnName, groupName))
-    {
-      mOnError(FormatString("Failed to get the name for the Animation group %d; ignoring.", numGroups));
-      continue;
-    }
-
-    auto iFind = std::lower_bound(animGroups.begin(), animGroups.end(), groupName, [](const AnimationGroupDefinition& group, const std::string& name) { return group.mName < name; });
-    if(iFind != animGroups.end() && iFind->mName == groupName)
-    {
-      mOnError(FormatString("Animation group with name '%s' already exists; new entries will be merged.", groupName.c_str()));
-    }
-    else
-    {
-      iFind = animGroups.insert(iFind, AnimationGroupDefinition{});
-    }
-
-    iFind->mName = groupName;
-
-    auto tnAnims = tnGroup.second.GetChild("animations");
-    if(tnAnims && tnAnims->Size() > 0)
-    {
-      auto& anims = iFind->mAnimations;
-      anims.reserve(anims.size() + tnAnims->Size());
-      for(auto iAnims = tnAnims->CBegin(), iAnimsEnd = tnAnims->CEnd(); iAnims != iAnimsEnd; ++iAnims)
-      {
-        anims.push_back((*iAnims).second.GetString());
-      }
-    }
-  }
-}
-
-void DliLoader::Impl::GetCameraParameters(std::vector<CameraParameters>& cameras) const
-{
-  if(mParser.GetRoot())
-  {
-    if(const TreeNode* jsonCameras = mParser.GetRoot()->GetChild("cameras"))
-    {
-      float dummyFloatArray[4];
-
-      cameras.resize(jsonCameras->Size());
-      auto iCamera = cameras.begin();
-      for(auto i0 = jsonCameras->CBegin(), i1 = jsonCameras->CEnd(); i0 != i1; ++i0)
-      {
-        auto& jsonCamera = (*i0).second;
-
-        ReadFloat(jsonCamera.GetChild("fov"), iCamera->yFovDegree.degree);
-        ReadFloat(jsonCamera.GetChild("near"), iCamera->zNear);
-        ReadFloat(jsonCamera.GetChild("far"), iCamera->zFar);
-        if(ReadVector(jsonCamera.GetChild("orthographic"), dummyFloatArray, 4u))
-        {
-          iCamera->isPerspective = false;
-
-          iCamera->orthographicSize = dummyFloatArray[2] * 0.5f;
-          iCamera->aspectRatio      = dummyFloatArray[1] / dummyFloatArray[2];
-        }
-
-        if(auto jsonMatrix = jsonCamera.GetChild("matrix"))
-        {
-          ReadVector(jsonMatrix, iCamera->matrix.AsFloat(), 16u);
-        }
-
-        ++iCamera;
-      }
-    }
-  }
-}
-
-void DliLoader::Impl::GetLightParameters(std::vector<LightParameters>& lights) const
-{
-  if(mParser.GetRoot())
-  {
-    if(const TreeNode* jsonLights = mParser.GetRoot()->GetChild("lights"))
-    {
-      lights.resize(jsonLights->Size());
-      auto iLight = lights.begin();
-      for(auto i0 = jsonLights->CBegin(), i1 = jsonLights->CEnd(); i0 != i1; ++i0)
-      {
-        auto& jsonLight = (*i0).second;
-        if(!ReadVector(jsonLight.GetChild("matrix"), iLight->transform.AsFloat(), 16))
-        {
-          mOnError(
-            FormatString("Failed to parse light %d - \"matrix\" child with 16 floats expected.\n",
-                         std::distance(jsonLights->CBegin(), i0)));
-          continue;
-        }
-
-        int shadowMapSize = 0;
-        if(ReadInt(jsonLight.GetChild(SHADOW_MAP_SIZE), shadowMapSize) && shadowMapSize < 0)
-        {
-          mOnError(
-            FormatString("Failed to parse light %d - %s has an invalid value.",
-                         std::distance(jsonLights->CBegin(), i0),
-                         SHADOW_MAP_SIZE));
-          continue;
-        }
-        iLight->shadowMapSize = shadowMapSize;
-
-        float orthoSize = 0.f;
-        if(ReadFloat(jsonLight.GetChild(ORTHOGRAPHIC_SIZE), orthoSize) &&
-           (orthoSize < .0f || std::isnan(orthoSize) || std::isinf(orthoSize)))
-        {
-          mOnError(
-            FormatString("Failed to parse light %d - %s has an invalid value.",
-                         std::distance(jsonLights->CBegin(), i0),
-                         ORTHOGRAPHIC_SIZE));
-          continue;
-        }
-        iLight->orthographicSize = orthoSize;
-
-        if((iLight->shadowMapSize > 0) != (iLight->orthographicSize > .0f))
-        {
-          mOnError(FormatString(
-            "Light %d: Both shadow map size and orthographic size must be set for shadows to work.",
-            std::distance(jsonLights->CBegin(), i0)));
-        }
-
-        if(!ReadVector(jsonLight.GetChild("color"), iLight->color.AsFloat(), 3)) // color is optional
-        {
-          iLight->color = Vector3::ONE; // default to white
-        }
-
-        if(!ReadFloat(jsonLight.GetChild("intensity"), iLight->intensity)) // intensity is optional
-        {
-          iLight->intensity = 1.0f; // default to 1.0
-        }
-
-        if(!ReadFloat(jsonLight.GetChild("shadowIntensity"), iLight->shadowIntensity)) // intensity is optional
-        {
-          iLight->shadowIntensity = 1.0f; // default to 1.0
-        }
-
-        ++iLight;
-      }
-    }
-  }
-}
-
-} // namespace Loader
-} // namespace Scene3D
-} // namespace Dali
diff --git a/dali-scene3d/public-api/loader/dli-loader.h b/dali-scene3d/public-api/loader/dli-loader.h
deleted file mode 100644 (file)
index c516f9a..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-#ifndef DALI_SCENE3D_LOADER_DLI_LOADER_H
-#define DALI_SCENE3D_LOADER_DLI_LOADER_H
-/*
- * Copyright (c) 2022 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/animation-definition.h"
-#include "dali-scene3d/public-api/loader/customization.h"
-#include "dali-scene3d/public-api/loader/index.h"
-#include "dali-scene3d/public-api/loader/string-callback.h"
-
-// EXTERNAL INCLUDES
-#include "dali/public-api/common/vector-wrapper.h"
-
-namespace Dali
-{
-namespace Scene3D
-{
-namespace Loader
-{
-typedef std::pair<std::string, std::string> Metadata;
-
-// Forward declarations
-struct LoadResult;
-struct CameraParameters;
-struct LightParameters;
-struct TextParameters;
-
-class ResourceBundle;
-struct NodeDefinition;
-class SceneDefinition;
-
-class DALI_SCENE3D_API DliLoader
-{
-public:
-  using ConvertFontCode  = void (*)(const std::string& code, std::string& fontFamily, std::string& slant, std::string& weight, float& size);
-  using ConvertColorCode = Vector4 (*)(const std::string& code);
-
-  using CategoryProcessor       = std::function<void(Property::Array&& categoryData, StringCallback onError)>;
-  using CategoryProcessorVector = std::vector<std::pair<std::string /*name*/, CategoryProcessor>>;
-
-  using NodeProcessor = std::function<void(const NodeDefinition& nodeDef,
-                                           Property::Map&&       nodeData,
-                                           StringCallback        onError)>;
-
-  using AnimationProcessor = std::function<void(const AnimationDefinition& animDef,
-                                                Property::Map&&            animData,
-                                                StringCallback             onError)>;
-
-  struct InputParams
-  {
-    /**
-     * @brief The absolute path of animation binaries referenced in the .dli.
-     */
-    std::string mAnimationsPath;
-
-    /**
-     * @brief Provides a facility to determine a color from a code instead of RGB(A) values.
-     */
-    ConvertColorCode mConvertColorCode;
-
-    /**
-     * @brief A collection of handlers, mapped to the names of the top level (i.e. below
-     *  root) element, whom they will attempt to process. This will take place before
-     *  the parsing of scene Nodes and Animations, but after skeletons, environment, mesh,
-     *  shader and material resources.
-     */
-    CategoryProcessorVector mPreNodeCategoryProcessors;
-
-    /**
-     * @brief A collection of handlers, mapped to the names of the top level (i.e. below
-     *  root) element, whom they will attempt to process. This will take place after
-     *  the parsing of the scene Nodes and Animations.
-     */
-    CategoryProcessorVector mPostNodeCategoryProcessors;
-
-    /**
-     * @brief Provides an extension point to nodes. If provided, this function will be
-     *  called with each JSON element and definition, of a scene node.
-     * @note Constraints rely on ID resolution (from .dli to scene definition), which
-     *  takes place after the parsing of the nodes; therefore AT THIS POINT the node
-     *  IDs seen in constraints will still be the .dli IDs - NOT to be relied on for
-     *  indexing into mScene.
-     */
-    NodeProcessor mNodePropertyProcessor;
-
-    /**
-     * @brief Provides an extension point to animations. If provided, this function will be
-     *  called with each JSON element and fully processed definition, of an animation.
-     */
-    AnimationProcessor mAnimationPropertyProcessor;
-  };
-
-  struct LoadParams
-  {
-    InputParams const& input;
-    LoadResult&        output;
-  };
-
-  DliLoader();
-  ~DliLoader();
-
-  /**
-   * @brief Sets the callback that messages from non-fatal errors get posted to.
-   *  Uses DefaultErrorCallback by default.
-   */
-  void SetErrorCallback(StringCallback onError);
-
-  /**
-   * @brief Attempts to load and parse a .dli document into a DOM tree.
-   * @return Whether the operation was successful.
-   */
-  bool LoadScene(const std::string& uri, LoadParams& params);
-
-  /**
-   * @return The error string describing how the parse has failed, if any.
-   */
-  std::string GetParseError() const;
-
-private:
-  struct Impl;
-  const std::unique_ptr<Impl> mImpl;
-};
-
-} // namespace Loader
-} // namespace Scene3D
-} // namespace Dali
-
-#endif // DALI_SCENE3D_LOADER_DLI_LOADER_H
diff --git a/dali-scene3d/public-api/loader/gltf2-loader.cpp b/dali-scene3d/public-api/loader/gltf2-loader.cpp
deleted file mode 100644 (file)
index 0744101..0000000
+++ /dev/null
@@ -1,1388 +0,0 @@
-/*
- * 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.
- *
- */
-
-// FILE HEADER
-#include <dali-scene3d/public-api/loader/gltf2-loader.h>
-
-// EXTERNAL INCLUDES
-#include <dali/devel-api/threading/mutex.h>
-#include <dali/integration-api/debug.h>
-#include <dali/public-api/images/image-operations.h>
-#include <dali/public-api/math/quaternion.h>
-#include <memory>
-
-// INTERNAL INCLUDES
-#include <dali-scene3d/internal/loader/gltf2-asset.h>
-#include <dali-scene3d/public-api/loader/load-result.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-definition-factory.h>
-#include <dali-scene3d/public-api/loader/utils.h>
-
-namespace gt = gltf2;
-namespace js = json;
-
-namespace Dali
-{
-namespace Scene3D
-{
-namespace Loader
-{
-namespace
-{
-Dali::Mutex gInitializeMutex;
-Dali::Mutex gReadMutex;
-
-const std::string POSITION_PROPERTY("position");
-const std::string ORIENTATION_PROPERTY("orientation");
-const std::string SCALE_PROPERTY("scale");
-const std::string BLEND_SHAPE_WEIGHTS_UNIFORM("uBlendShapeWeight");
-const std::string MRENDERER_MODEL_IDENTIFICATION("M-Renderer");
-const std::string ROOT_NODE_NAME("RootNode");
-const Vector3     SCALE_TO_ADJUST(100.0f, 100.0f, 100.0f);
-
-const Geometry::Type GLTF2_TO_DALI_PRIMITIVES[]{
-  Geometry::POINTS,
-  Geometry::LINES,
-  Geometry::LINE_LOOP,
-  Geometry::LINE_STRIP,
-  Geometry::TRIANGLES,
-  Geometry::TRIANGLE_STRIP,
-  Geometry::TRIANGLE_FAN}; //...because Dali swaps the last two.
-
-struct AttributeMapping
-{
-  gt::Attribute::Type      mType;
-  MeshDefinition::Accessor MeshDefinition::*mAccessor;
-  uint16_t                                  mElementSizeRequired;
-} ATTRIBUTE_MAPPINGS[]{
-  {gt::Attribute::NORMAL, &MeshDefinition::mNormals, sizeof(Vector3)},
-  {gt::Attribute::TANGENT, &MeshDefinition::mTangents, sizeof(Vector3)},
-  {gt::Attribute::TEXCOORD_0, &MeshDefinition::mTexCoords, sizeof(Vector2)},
-  {gt::Attribute::COLOR_0, &MeshDefinition::mColors, sizeof(Vector4)},
-  {gt::Attribute::JOINTS_0, &MeshDefinition::mJoints0, sizeof(Vector4)},
-  {gt::Attribute::WEIGHTS_0, &MeshDefinition::mWeights0, sizeof(Vector4)},
-};
-
-std::vector<gt::Animation> ReadAnimationArray(const json_value_s& j)
-{
-  auto results = js::Read::Array<gt::Animation, js::ObjectReader<gt::Animation>::Read>(j);
-
-  for(auto& animation : results)
-  {
-    for(auto& channel : animation.mChannels)
-    {
-      channel.mSampler.UpdateVector(animation.mSamplers);
-    }
-  }
-
-  return results;
-}
-
-void ApplyAccessorMinMax(const gt::Accessor& acc, float* values)
-{
-  DALI_ASSERT_ALWAYS(acc.mMax.empty() || gt::AccessorType::ElementCount(acc.mType) == acc.mMax.size());
-  DALI_ASSERT_ALWAYS(acc.mMin.empty() || gt::AccessorType::ElementCount(acc.mType) == acc.mMin.size());
-  MeshDefinition::Blob::ApplyMinMax(acc.mMin, acc.mMax, acc.mCount, values);
-}
-
-const auto BUFFER_READER = std::move(js::Reader<gt::Buffer>()
-                                       .Register(*js::MakeProperty("byteLength", js::Read::Number<uint32_t>, &gt::Buffer::mByteLength))
-                                       .Register(*js::MakeProperty("uri", js::Read::StringView, &gt::Buffer::mUri)));
-
-const auto BUFFER_VIEW_READER = std::move(js::Reader<gt::BufferView>()
-                                            .Register(*js::MakeProperty("buffer", gt::RefReader<gt::Document>::Read<gt::Buffer, &gt::Document::mBuffers>, &gt::BufferView::mBuffer))
-                                            .Register(*js::MakeProperty("byteOffset", js::Read::Number<uint32_t>, &gt::BufferView::mByteOffset))
-                                            .Register(*js::MakeProperty("byteLength", js::Read::Number<uint32_t>, &gt::BufferView::mByteLength))
-                                            .Register(*js::MakeProperty("byteStride", js::Read::Number<uint32_t>, &gt::BufferView::mByteStride))
-                                            .Register(*js::MakeProperty("target", js::Read::Number<uint32_t>, &gt::BufferView::mTarget)));
-
-const auto BUFFER_VIEW_CLIENT_READER = std::move(js::Reader<gt::BufferViewClient>()
-                                                   .Register(*js::MakeProperty("bufferView", gt::RefReader<gt::Document>::Read<gt::BufferView, &gt::Document::mBufferViews>, &gt::BufferViewClient::mBufferView))
-                                                   .Register(*js::MakeProperty("byteOffset", js::Read::Number<uint32_t>, &gt::BufferViewClient::mByteOffset)));
-
-const auto COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER = std::move(js::Reader<gt::ComponentTypedBufferViewClient>()
-                                                                   .Register(*new js::Property<gt::ComponentTypedBufferViewClient, gt::Ref<gt::BufferView>>("bufferView", gt::RefReader<gt::Document>::Read<gt::BufferView, &gt::Document::mBufferViews>, &gt::ComponentTypedBufferViewClient::mBufferView))
-                                                                   .Register(*new js::Property<gt::ComponentTypedBufferViewClient, uint32_t>("byteOffset", js::Read::Number<uint32_t>, &gt::ComponentTypedBufferViewClient::mByteOffset))
-                                                                   .Register(*js::MakeProperty("componentType", js::Read::Enum<gt::Component::Type>, &gt::ComponentTypedBufferViewClient::mComponentType)));
-
-const auto ACCESSOR_SPARSE_READER = std::move(js::Reader<gt::Accessor::Sparse>()
-                                                .Register(*js::MakeProperty("count", js::Read::Number<uint32_t>, &gt::Accessor::Sparse::mCount))
-                                                .Register(*js::MakeProperty("indices", js::ObjectReader<gt::ComponentTypedBufferViewClient>::Read, &gt::Accessor::Sparse::mIndices))
-                                                .Register(*js::MakeProperty("values", js::ObjectReader<gt::BufferViewClient>::Read, &gt::Accessor::Sparse::mValues)));
-
-const auto ACCESSOR_READER = std::move(js::Reader<gt::Accessor>()
-                                         .Register(*new js::Property<gt::Accessor, gt::Ref<gt::BufferView>>("bufferView",
-                                                                                                            gt::RefReader<gt::Document>::Read<gt::BufferView, &gt::Document::mBufferViews>,
-                                                                                                            &gt::Accessor::mBufferView))
-                                         .Register(*new js::Property<gt::Accessor, uint32_t>("byteOffset",
-                                                                                             js::Read::Number<uint32_t>,
-                                                                                             &gt::Accessor::mByteOffset))
-                                         .Register(*new js::Property<gt::Accessor, gt::Component::Type>("componentType",
-                                                                                                        js::Read::Enum<gt::Component::Type>,
-                                                                                                        &gt::Accessor::mComponentType))
-                                         .Register(*new js::Property<gt::Accessor, std::string_view>("name", js::Read::StringView, &gt::Accessor::mName))
-                                         .Register(*js::MakeProperty("count", js::Read::Number<uint32_t>, &gt::Accessor::mCount))
-                                         .Register(*js::MakeProperty("normalized", js::Read::Boolean, &gt::Accessor::mNormalized))
-                                         .Register(*js::MakeProperty("type", gt::ReadStringEnum<gt::AccessorType>, &gt::Accessor::mType))
-                                         .Register(*js::MakeProperty("min", js::Read::Array<float, js::Read::Number>, &gt::Accessor::mMin))
-                                         .Register(*js::MakeProperty("max", js::Read::Array<float, js::Read::Number>, &gt::Accessor::mMax))
-                                         .Register(*new js::Property<gt::Accessor, gt::Accessor::Sparse>("sparse", js::ObjectReader<gt::Accessor::Sparse>::Read, &gt::Accessor::SetSparse)));
-
-const auto IMAGE_READER = std::move(js::Reader<gt::Image>()
-                                      .Register(*new js::Property<gt::Image, std::string_view>("name", js::Read::StringView, &gt::Material::mName))
-                                      .Register(*js::MakeProperty("uri", js::Read::StringView, &gt::Image::mUri))
-                                      .Register(*js::MakeProperty("mimeType", js::Read::StringView, &gt::Image::mMimeType))
-                                      .Register(*js::MakeProperty("bufferView", gt::RefReader<gt::Document>::Read<gt::BufferView, &gt::Document::mBufferViews>, &gt::Image::mBufferView)));
-
-const auto SAMPLER_READER = std::move(js::Reader<gt::Sampler>()
-                                        .Register(*js::MakeProperty("minFilter", js::Read::Enum<gt::Filter::Type>, &gt::Sampler::mMinFilter))
-                                        .Register(*js::MakeProperty("magFilter", js::Read::Enum<gt::Filter::Type>, &gt::Sampler::mMagFilter))
-                                        .Register(*js::MakeProperty("wrapS", js::Read::Enum<gt::Wrap::Type>, &gt::Sampler::mWrapS))
-                                        .Register(*js::MakeProperty("wrapT", js::Read::Enum<gt::Wrap::Type>, &gt::Sampler::mWrapT)));
-
-const auto TEXURE_READER = std::move(js::Reader<gt::Texture>()
-                                       .Register(*js::MakeProperty("source", gt::RefReader<gt::Document>::Read<gt::Image, &gt::Document::mImages>, &gt::Texture::mSource))
-                                       .Register(*js::MakeProperty("sampler", gt::RefReader<gt::Document>::Read<gt::Sampler, &gt::Document::mSamplers>, &gt::Texture::mSampler)));
-
-const auto TEXURE_INFO_READER = std::move(js::Reader<gt::TextureInfo>()
-                                            .Register(*js::MakeProperty("index", gt::RefReader<gt::Document>::Read<gt::Texture, &gt::Document::mTextures>, &gt::TextureInfo::mTexture))
-                                            .Register(*js::MakeProperty("texCoord", js::Read::Number<uint32_t>, &gt::TextureInfo::mTexCoord))
-                                            .Register(*js::MakeProperty("scale", js::Read::Number<float>, &gt::TextureInfo::mScale))
-                                            .Register(*js::MakeProperty("strength", js::Read::Number<float>, &gt::TextureInfo::mStrength)));
-
-const auto MATERIAL_PBR_READER = std::move(js::Reader<gt::Material::Pbr>()
-                                             .Register(*js::MakeProperty("baseColorFactor", gt::ReadDaliVector<Vector4>, &gt::Material::Pbr::mBaseColorFactor))
-                                             .Register(*js::MakeProperty("baseColorTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::Material::Pbr::mBaseColorTexture))
-                                             .Register(*js::MakeProperty("metallicFactor", js::Read::Number<float>, &gt::Material::Pbr::mMetallicFactor))
-                                             .Register(*js::MakeProperty("roughnessFactor", js::Read::Number<float>, &gt::Material::Pbr::mRoughnessFactor))
-                                             .Register(*js::MakeProperty("metallicRoughnessTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::Material::Pbr::mMetallicRoughnessTexture)));
-
-const auto MATERIAL_SPECULAR_READER = std::move(js::Reader<gt::MaterialSpecular>()
-                                                  .Register(*js::MakeProperty("specularFactor", js::Read::Number<float>, &gt::MaterialSpecular::mSpecularFactor))
-                                                  .Register(*js::MakeProperty("specularTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::MaterialSpecular::mSpecularTexture))
-                                                  .Register(*js::MakeProperty("specularColorFactor", gt::ReadDaliVector<Vector3>, &gt::MaterialSpecular::mSpecularColorFactor))
-                                                  .Register(*js::MakeProperty("specularColorTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::MaterialSpecular::mSpecularColorTexture)));
-
-const auto MATERIAL_IOR_READER = std::move(js::Reader<gt::MaterialIor>()
-                                             .Register(*js::MakeProperty("ior", js::Read::Number<float>, &gt::MaterialIor::mIor)));
-
-const auto MATERIAL_EXTENSION_READER = std::move(js::Reader<gt::MaterialExtensions>()
-                                                   .Register(*js::MakeProperty("KHR_materials_ior", js::ObjectReader<gt::MaterialIor>::Read, &gt::MaterialExtensions::mMaterialIor))
-                                                   .Register(*js::MakeProperty("KHR_materials_specular", js::ObjectReader<gt::MaterialSpecular>::Read, &gt::MaterialExtensions::mMaterialSpecular)));
-
-const auto MATERIAL_READER = std::move(js::Reader<gt::Material>()
-                                         .Register(*new js::Property<gt::Material, std::string_view>("name", js::Read::StringView, &gt::Material::mName))
-                                         .Register(*js::MakeProperty("pbrMetallicRoughness", js::ObjectReader<gt::Material::Pbr>::Read, &gt::Material::mPbrMetallicRoughness))
-                                         .Register(*js::MakeProperty("normalTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::Material::mNormalTexture))
-                                         .Register(*js::MakeProperty("occlusionTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::Material::mOcclusionTexture))
-                                         .Register(*js::MakeProperty("emissiveTexture", js::ObjectReader<gt::TextureInfo>::Read, &gt::Material::mEmissiveTexture))
-                                         .Register(*js::MakeProperty("emissiveFactor", gt::ReadDaliVector<Vector3>, &gt::Material::mEmissiveFactor))
-                                         .Register(*js::MakeProperty("alphaMode", gt::ReadStringEnum<gt::AlphaMode>, &gt::Material::mAlphaMode))
-                                         .Register(*js::MakeProperty("alphaCutoff", js::Read::Number<float>, &gt::Material::mAlphaCutoff))
-                                         .Register(*js::MakeProperty("doubleSided", js::Read::Boolean, &gt::Material::mDoubleSided))
-                                         .Register(*js::MakeProperty("extensions", js::ObjectReader<gt::MaterialExtensions>::Read, &gt::Material::mMaterialExtensions)));
-
-std::map<gt::Attribute::Type, gt::Ref<gt::Accessor>> ReadMeshPrimitiveAttributes(const json_value_s& j)
-{
-  auto&                                                jo = js::Cast<json_object_s>(j);
-  std::map<gt::Attribute::Type, gt::Ref<gt::Accessor>> result;
-
-  auto i = jo.start;
-  while(i)
-  {
-    auto jstr                                                        = *i->name;
-    result[gt::Attribute::FromString(jstr.string, jstr.string_size)] = gt::RefReader<gt::Document>::Read<gt::Accessor, &gt::Document::mAccessors>(*i->value);
-    i                                                                = i->next;
-  }
-  return result;
-}
-
-std::vector<std::map<gt::Attribute::Type, gt::Ref<gt::Accessor>>> ReadMeshPrimitiveTargets(const json_value_s& j)
-{
-  auto&                                                             jo = js::Cast<json_array_s>(j);
-  std::vector<std::map<gt::Attribute::Type, gt::Ref<gt::Accessor>>> result;
-
-  result.reserve(jo.length);
-
-  auto i = jo.start;
-  while(i)
-  {
-    result.push_back(std::move(ReadMeshPrimitiveAttributes(*i->value)));
-    i = i->next;
-  }
-
-  return result;
-}
-
-const auto MESH_PRIMITIVE_READER = std::move(js::Reader<gt::Mesh::Primitive>()
-                                               .Register(*js::MakeProperty("attributes", ReadMeshPrimitiveAttributes, &gt::Mesh::Primitive::mAttributes))
-                                               .Register(*js::MakeProperty("indices", gt::RefReader<gt::Document>::Read<gt::Accessor, &gt::Document::mAccessors>, &gt::Mesh::Primitive::mIndices))
-                                               .Register(*js::MakeProperty("material", gt::RefReader<gt::Document>::Read<gt::Material, &gt::Document::mMaterials>, &gt::Mesh::Primitive::mMaterial))
-                                               .Register(*js::MakeProperty("mode", js::Read::Enum<gt::Mesh::Primitive::Mode>, &gt::Mesh::Primitive::mMode))
-                                               .Register(*js::MakeProperty("targets", ReadMeshPrimitiveTargets, &gt::Mesh::Primitive::mTargets)));
-
-const auto MESH_READER = std::move(js::Reader<gt::Mesh>()
-                                     .Register(*new js::Property<gt::Mesh, std::string_view>("name", js::Read::StringView, &gt::Mesh::mName))
-                                     .Register(*js::MakeProperty("primitives",
-                                                                 js::Read::Array<gt::Mesh::Primitive, js::ObjectReader<gt::Mesh::Primitive>::Read>,
-                                                                 &gt::Mesh::mPrimitives))
-                                     .Register(*js::MakeProperty("weights", js::Read::Array<float, js::Read::Number>, &gt::Mesh::mWeights)));
-
-const auto SKIN_READER = std::move(js::Reader<gt::Skin>()
-                                     .Register(*new js::Property<gt::Skin, std::string_view>("name", js::Read::StringView, &gt::Skin::mName))
-                                     .Register(*js::MakeProperty("inverseBindMatrices",
-                                                                 gt::RefReader<gt::Document>::Read<gt::Accessor, &gt::Document::mAccessors>,
-                                                                 &gt::Skin::mInverseBindMatrices))
-                                     .Register(*js::MakeProperty("skeleton",
-                                                                 gt::RefReader<gt::Document>::Read<gt::Node, &gt::Document::mNodes>,
-                                                                 &gt::Skin::mSkeleton))
-                                     .Register(*js::MakeProperty("joints",
-                                                                 js::Read::Array<gt::Ref<gt::Node>, gt::RefReader<gt::Document>::Read<gt::Node, &gt::Document::mNodes>>,
-                                                                 &gt::Skin::mJoints)));
-
-const auto CAMERA_PERSPECTIVE_READER = std::move(js::Reader<gt::Camera::Perspective>()
-                                                   .Register(*js::MakeProperty("aspectRatio", js::Read::Number<float>, &gt::Camera::Perspective::mAspectRatio))
-                                                   .Register(*js::MakeProperty("yfov", js::Read::Number<float>, &gt::Camera::Perspective::mYFov))
-                                                   .Register(*js::MakeProperty("zfar", js::Read::Number<float>, &gt::Camera::Perspective::mZFar))
-                                                   .Register(*js::MakeProperty("znear", js::Read::Number<float>, &gt::Camera::Perspective::mZNear))); // TODO: infinite perspective projection, where znear is omitted
-
-const auto CAMERA_ORTHOGRAPHIC_READER = std::move(js::Reader<gt::Camera::Orthographic>()
-                                                    .Register(*js::MakeProperty("xmag", js::Read::Number<float>, &gt::Camera::Orthographic::mXMag))
-                                                    .Register(*js::MakeProperty("ymag", js::Read::Number<float>, &gt::Camera::Orthographic::mYMag))
-                                                    .Register(*js::MakeProperty("zfar", js::Read::Number<float>, &gt::Camera::Orthographic::mZFar))
-                                                    .Register(*js::MakeProperty("znear", js::Read::Number<float>, &gt::Camera::Orthographic::mZNear)));
-
-const auto CAMERA_READER = std::move(js::Reader<gt::Camera>()
-                                       .Register(*new js::Property<gt::Camera, std::string_view>("name", js::Read::StringView, &gt::Camera::mName))
-                                       .Register(*js::MakeProperty("type", js::Read::StringView, &gt::Camera::mType))
-                                       .Register(*js::MakeProperty("perspective", js::ObjectReader<gt::Camera::Perspective>::Read, &gt::Camera::mPerspective))
-                                       .Register(*js::MakeProperty("orthographic", js::ObjectReader<gt::Camera::Orthographic>::Read, &gt::Camera::mOrthographic)));
-
-const auto NODE_READER = std::move(js::Reader<gt::Node>()
-                                     .Register(*new js::Property<gt::Node, std::string_view>("name", js::Read::StringView, &gt::Node::mName))
-                                     .Register(*js::MakeProperty("translation", gt::ReadDaliVector<Vector3>, &gt::Node::mTranslation))
-                                     .Register(*js::MakeProperty("rotation", gt::ReadQuaternion, &gt::Node::mRotation))
-                                     .Register(*js::MakeProperty("scale", gt::ReadDaliVector<Vector3>, &gt::Node::mScale))
-                                     .Register(*new js::Property<gt::Node, Matrix>("matrix", gt::ReadDaliVector<Matrix>, &gt::Node::SetMatrix))
-                                     .Register(*js::MakeProperty("camera", gt::RefReader<gt::Document>::Read<gt::Camera, &gt::Document::mCameras>, &gt::Node::mCamera))
-                                     .Register(*js::MakeProperty("children", js::Read::Array<gt::Ref<gt::Node>, gt::RefReader<gt::Document>::Read<gt::Node, &gt::Document::mNodes>>, &gt::Node::mChildren))
-                                     .Register(*js::MakeProperty("mesh", gt::RefReader<gt::Document>::Read<gt::Mesh, &gt::Document::mMeshes>, &gt::Node::mMesh))
-                                     .Register(*js::MakeProperty("skin", gt::RefReader<gt::Document>::Read<gt::Skin, &gt::Document::mSkins>, &gt::Node::mSkin)));
-
-const auto ANIMATION_SAMPLER_READER = std::move(js::Reader<gt::Animation::Sampler>()
-                                                  .Register(*js::MakeProperty("input", gt::RefReader<gt::Document>::Read<gt::Accessor, &gt::Document::mAccessors>, &gt::Animation::Sampler::mInput))
-                                                  .Register(*js::MakeProperty("output", gt::RefReader<gt::Document>::Read<gt::Accessor, &gt::Document::mAccessors>, &gt::Animation::Sampler::mOutput))
-                                                  .Register(*js::MakeProperty("interpolation", gt::ReadStringEnum<gt::Animation::Sampler::Interpolation>, &gt::Animation::Sampler::mInterpolation)));
-
-const auto ANIMATION_TARGET_READER = std::move(js::Reader<gt::Animation::Channel::Target>()
-                                                 .Register(*js::MakeProperty("node", gt::RefReader<gt::Document>::Read<gt::Node, &gt::Document::mNodes>, &gt::Animation::Channel::Target::mNode))
-                                                 .Register(*js::MakeProperty("path", gt::ReadStringEnum<gt::Animation::Channel::Target>, &gt::Animation::Channel::Target::mPath)));
-
-const auto ANIMATION_CHANNEL_READER = std::move(js::Reader<gt::Animation::Channel>()
-                                                  .Register(*js::MakeProperty("target", js::ObjectReader<gt::Animation::Channel::Target>::Read, &gt::Animation::Channel::mTarget))
-                                                  .Register(*js::MakeProperty("sampler", gt::RefReader<gt::Animation>::Read<gt::Animation::Sampler, &gt::Animation::mSamplers>, &gt::Animation::Channel::mSampler)));
-
-const auto ANIMATION_READER = std::move(js::Reader<gt::Animation>()
-                                          .Register(*new js::Property<gt::Animation, std::string_view>("name", js::Read::StringView, &gt::Animation::mName))
-                                          .Register(*js::MakeProperty("samplers",
-                                                                      js::Read::Array<gt::Animation::Sampler, js::ObjectReader<gt::Animation::Sampler>::Read>,
-                                                                      &gt::Animation::mSamplers))
-                                          .Register(*js::MakeProperty("channels",
-                                                                      js::Read::Array<gt::Animation::Channel, js::ObjectReader<gt::Animation::Channel>::Read>,
-                                                                      &gt::Animation::mChannels)));
-
-const auto SCENE_READER = std::move(js::Reader<gt::Scene>()
-                                      .Register(*new js::Property<gt::Scene, std::string_view>("name", js::Read::StringView, &gt::Scene::mName))
-                                      .Register(*js::MakeProperty("nodes",
-                                                                  js::Read::Array<gt::Ref<gt::Node>, gt::RefReader<gt::Document>::Read<gt::Node, &gt::Document::mNodes>>,
-                                                                  &gt::Scene::mNodes)));
-
-const auto DOCUMENT_READER = std::move(js::Reader<gt::Document>()
-                                         .Register(*js::MakeProperty("buffers",
-                                                                     js::Read::Array<gt::Buffer, js::ObjectReader<gt::Buffer>::Read>,
-                                                                     &gt::Document::mBuffers))
-                                         .Register(*js::MakeProperty("bufferViews",
-                                                                     js::Read::Array<gt::BufferView, js::ObjectReader<gt::BufferView>::Read>,
-                                                                     &gt::Document::mBufferViews))
-                                         .Register(*js::MakeProperty("accessors",
-                                                                     js::Read::Array<gt::Accessor, js::ObjectReader<gt::Accessor>::Read>,
-                                                                     &gt::Document::mAccessors))
-                                         .Register(*js::MakeProperty("images",
-                                                                     js::Read::Array<gt::Image, js::ObjectReader<gt::Image>::Read>,
-                                                                     &gt::Document::mImages))
-                                         .Register(*js::MakeProperty("samplers",
-                                                                     js::Read::Array<gt::Sampler, js::ObjectReader<gt::Sampler>::Read>,
-                                                                     &gt::Document::mSamplers))
-                                         .Register(*js::MakeProperty("textures",
-                                                                     js::Read::Array<gt::Texture, js::ObjectReader<gt::Texture>::Read>,
-                                                                     &gt::Document::mTextures))
-                                         .Register(*js::MakeProperty("materials",
-                                                                     js::Read::Array<gt::Material, js::ObjectReader<gt::Material>::Read>,
-                                                                     &gt::Document::mMaterials))
-                                         .Register(*js::MakeProperty("meshes",
-                                                                     js::Read::Array<gt::Mesh, js::ObjectReader<gt::Mesh>::Read>,
-                                                                     &gt::Document::mMeshes))
-                                         .Register(*js::MakeProperty("skins",
-                                                                     js::Read::Array<gt::Skin, js::ObjectReader<gt::Skin>::Read>,
-                                                                     &gt::Document::mSkins))
-                                         .Register(*js::MakeProperty("cameras",
-                                                                     js::Read::Array<gt::Camera, js::ObjectReader<gt::Camera>::Read>,
-                                                                     &gt::Document::mCameras))
-                                         .Register(*js::MakeProperty("nodes",
-                                                                     js::Read::Array<gt::Node, js::ObjectReader<gt::Node>::Read>,
-                                                                     &gt::Document::mNodes))
-                                         .Register(*js::MakeProperty("animations",
-                                                                     ReadAnimationArray,
-                                                                     &gt::Document::mAnimations))
-                                         .Register(*js::MakeProperty("scenes",
-                                                                     js::Read::Array<gt::Scene, js::ObjectReader<gt::Scene>::Read>,
-                                                                     &gt::Document::mScenes))
-                                         .Register(*js::MakeProperty("scene", gt::RefReader<gt::Document>::Read<gt::Scene, &gt::Document::mScenes>, &gt::Document::mScene)));
-
-struct NodeMapping
-{
-  Index gltfIdx;
-  Index runtimeIdx;
-};
-
-bool operator<(const NodeMapping& mapping, Index gltfIdx)
-{
-  return mapping.gltfIdx < gltfIdx;
-}
-
-class NodeIndexMapper
-{
-public:
-  NodeIndexMapper()                       = default;
-  NodeIndexMapper(const NodeIndexMapper&) = delete;
-  NodeIndexMapper& operator=(const NodeIndexMapper&) = delete;
-
-  ///@brief Registers a mapping of the @a gltfIdx of a node to its @a runtimeIdx .
-  ///@note If the indices are the same, the registration is omitted, in order to
-  /// save growing a vector.
-  void RegisterMapping(Index gltfIdx, Index runtimeIdx)
-  {
-    if(gltfIdx != runtimeIdx)
-    {
-      auto iInsert = std::lower_bound(mNodes.begin(), mNodes.end(), gltfIdx);
-      DALI_ASSERT_DEBUG(iInsert == mNodes.end() || iInsert->gltfIdx != gltfIdx);
-      mNodes.insert(iInsert, NodeMapping{gltfIdx, runtimeIdx});
-    }
-  }
-
-  ///@brief Retrieves the runtime index of a Node, mapped to the given @a gltfIdx.
-  Index GetRuntimeId(Index gltfIdx) const
-  {
-    auto iFind = std::lower_bound(mNodes.begin(), mNodes.end(), gltfIdx); // using custom operator<
-    return (iFind != mNodes.end() && iFind->gltfIdx == gltfIdx) ? iFind->runtimeIdx : gltfIdx;
-  }
-
-private:
-  std::vector<NodeMapping> mNodes;
-};
-
-struct ConversionContext
-{
-  LoadResult& mOutput;
-
-  std::string mPath;
-  Index       mDefaultMaterial;
-
-  std::vector<Index> mMeshIds;
-  NodeIndexMapper    mNodeIndices;
-};
-
-void ConvertBuffer(const gt::Buffer& buffer, decltype(ResourceBundle::mBuffers)& outBuffers, const std::string& resourcePath)
-{
-  BufferDefinition bufferDefinition;
-
-  bufferDefinition.mResourcePath = resourcePath;
-  bufferDefinition.mUri          = buffer.mUri;
-  bufferDefinition.mByteLength   = buffer.mByteLength;
-
-  outBuffers.emplace_back(std::move(bufferDefinition));
-}
-
-void ConvertBuffers(const gt::Document& doc, ConversionContext& context)
-{
-  auto& outBuffers = context.mOutput.mResources.mBuffers;
-  outBuffers.reserve(doc.mBuffers.size());
-
-  for(auto& buffer : doc.mBuffers)
-  {
-    ConvertBuffer(buffer, outBuffers, context.mPath);
-  }
-}
-
-SamplerFlags::Type ConvertWrapMode(gt::Wrap::Type wrapMode)
-{
-  switch(wrapMode)
-  {
-    case gt::Wrap::REPEAT:
-      return SamplerFlags::WRAP_REPEAT;
-    case gt::Wrap::CLAMP_TO_EDGE:
-      return SamplerFlags::WRAP_CLAMP;
-    case gt::Wrap::MIRRORED_REPEAT:
-      return SamplerFlags::WRAP_MIRROR;
-    default:
-      throw std::runtime_error("Invalid wrap type.");
-  }
-}
-
-SamplerFlags::Type ConvertSampler(const gt::Ref<gt::Sampler>& sampler)
-{
-  if(sampler)
-  {
-    return ((sampler->mMinFilter < gt::Filter::NEAREST_MIPMAP_NEAREST) ? (sampler->mMinFilter - gt::Filter::NEAREST) : ((sampler->mMinFilter - gt::Filter::NEAREST_MIPMAP_NEAREST) + 2)) |
-           ((sampler->mMagFilter - gt::Filter::NEAREST) << SamplerFlags::FILTER_MAG_SHIFT) |
-           (ConvertWrapMode(sampler->mWrapS) << SamplerFlags::WRAP_S_SHIFT) |
-           (ConvertWrapMode(sampler->mWrapT) << SamplerFlags::WRAP_T_SHIFT);
-  }
-  else
-  {
-    // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#texturesampler
-    // "The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering should be used."
-    // "What is an auto filtering", I hear you ask. Since there's nothing else to determine mipmapping from - including glTF image
-    // properties, if not in some extension -, we will simply assume linear filtering.
-    return SamplerFlags::FILTER_LINEAR | (SamplerFlags::FILTER_LINEAR << SamplerFlags::FILTER_MAG_SHIFT) |
-           (SamplerFlags::WRAP_REPEAT << SamplerFlags::WRAP_S_SHIFT) | (SamplerFlags::WRAP_REPEAT << SamplerFlags::WRAP_T_SHIFT);
-  }
-}
-
-TextureDefinition ConvertTextureInfo(const gt::TextureInfo& mm, ConversionContext& context, const ImageMetadata& metaData = ImageMetadata())
-{
-  TextureDefinition textureDefinition;
-  std::string       uri = std::string(mm.mTexture->mSource->mUri);
-  if(uri.empty())
-  {
-    uint32_t bufferIndex = mm.mTexture->mSource->mBufferView->mBuffer.GetIndex();
-    if(bufferIndex != INVALID_INDEX && context.mOutput.mResources.mBuffers[bufferIndex].IsAvailable())
-    {
-      auto& stream = context.mOutput.mResources.mBuffers[bufferIndex].GetBufferStream();
-      stream.clear();
-      stream.seekg(mm.mTexture->mSource->mBufferView->mByteOffset, stream.beg);
-      std::vector<uint8_t> dataBuffer;
-      dataBuffer.resize(mm.mTexture->mSource->mBufferView->mByteLength);
-      stream.read(reinterpret_cast<char*>(dataBuffer.data()), static_cast<std::streamsize>(static_cast<size_t>(mm.mTexture->mSource->mBufferView->mByteLength)));
-      return TextureDefinition{std::move(dataBuffer), ConvertSampler(mm.mTexture->mSampler), metaData.mMinSize, metaData.mSamplingMode};
-    }
-    return TextureDefinition();
-  }
-  else
-  {
-    return TextureDefinition{uri, ConvertSampler(mm.mTexture->mSampler), metaData.mMinSize, metaData.mSamplingMode};
-  }
-}
-
-void ConvertMaterial(const gt::Material& material, const std::unordered_map<std::string, ImageMetadata>& imageMetaData, decltype(ResourceBundle::mMaterials)& outMaterials, ConversionContext& context)
-{
-  auto getTextureMetaData = [](const std::unordered_map<std::string, ImageMetadata>& metaData, const gt::TextureInfo& info) {
-    if(!info.mTexture->mSource->mUri.empty())
-    {
-      if(auto search = metaData.find(info.mTexture->mSource->mUri.data()); search != metaData.end())
-      {
-        return search->second;
-      }
-    }
-    return ImageMetadata();
-  };
-
-  MaterialDefinition matDef;
-
-  auto& pbr = material.mPbrMetallicRoughness;
-  if(material.mAlphaMode == gt::AlphaMode::BLEND)
-  {
-    matDef.mIsOpaque = false;
-    matDef.mFlags |= MaterialDefinition::TRANSPARENCY;
-  }
-  else if(material.mAlphaMode == gt::AlphaMode::MASK)
-  {
-    matDef.mIsMask = true;
-    matDef.SetAlphaCutoff(std::min(1.f, std::max(0.f, material.mAlphaCutoff)));
-  }
-
-  matDef.mBaseColorFactor = pbr.mBaseColorFactor;
-
-  matDef.mTextureStages.reserve(!!pbr.mBaseColorTexture + !!pbr.mMetallicRoughnessTexture + !!material.mNormalTexture + !!material.mOcclusionTexture + !!material.mEmissiveTexture);
-  if(pbr.mBaseColorTexture)
-  {
-    const auto semantic = MaterialDefinition::ALBEDO;
-    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(pbr.mBaseColorTexture, context, getTextureMetaData(imageMetaData, pbr.mBaseColorTexture))});
-    // TODO: and there had better be one
-    matDef.mFlags |= semantic;
-  }
-  else
-  {
-    matDef.mNeedAlbedoTexture = false;
-  }
-
-  matDef.mMetallic  = pbr.mMetallicFactor;
-  matDef.mRoughness = pbr.mRoughnessFactor;
-
-  if(pbr.mMetallicRoughnessTexture)
-  {
-    const auto semantic = MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS |
-                          MaterialDefinition::GLTF_CHANNELS;
-    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(pbr.mMetallicRoughnessTexture, context, getTextureMetaData(imageMetaData, pbr.mMetallicRoughnessTexture))});
-    // TODO: and there had better be one
-    matDef.mFlags |= semantic;
-  }
-  else
-  {
-    matDef.mNeedMetallicRoughnessTexture = false;
-  }
-
-  matDef.mNormalScale = material.mNormalTexture.mScale;
-  if(material.mNormalTexture)
-  {
-    const auto semantic = MaterialDefinition::NORMAL;
-    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mNormalTexture, context, getTextureMetaData(imageMetaData, material.mNormalTexture))});
-    // TODO: and there had better be one
-    matDef.mFlags |= semantic;
-  }
-  else
-  {
-    matDef.mNeedNormalTexture = false;
-  }
-
-  if(material.mOcclusionTexture)
-  {
-    const auto semantic = MaterialDefinition::OCCLUSION;
-    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mOcclusionTexture, context, getTextureMetaData(imageMetaData, material.mOcclusionTexture))});
-    // TODO: and there had better be one
-    matDef.mFlags |= semantic;
-    matDef.mOcclusionStrength = material.mOcclusionTexture.mStrength;
-  }
-
-  if(material.mEmissiveTexture)
-  {
-    const auto semantic = MaterialDefinition::EMISSIVE;
-    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mEmissiveTexture, context, getTextureMetaData(imageMetaData, material.mEmissiveTexture))});
-    // TODO: and there had better be one
-    matDef.mFlags |= semantic;
-    matDef.mEmissiveFactor = material.mEmissiveFactor;
-  }
-
-  if(!Dali::Equals(material.mMaterialExtensions.mMaterialIor.mIor, gltf2::UNDEFINED_FLOAT_VALUE))
-  {
-    float ior                  = material.mMaterialExtensions.mMaterialIor.mIor;
-    matDef.mDielectricSpecular = powf((ior - 1.0f) / (ior + 1.0f), 2.0f);
-  }
-  matDef.mSpecularFactor      = material.mMaterialExtensions.mMaterialSpecular.mSpecularFactor;
-  matDef.mSpecularColorFactor = material.mMaterialExtensions.mMaterialSpecular.mSpecularColorFactor;
-
-  if(material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture)
-  {
-    const auto semantic = MaterialDefinition::SPECULAR;
-    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture, context, getTextureMetaData(imageMetaData, material.mMaterialExtensions.mMaterialSpecular.mSpecularTexture))});
-    matDef.mFlags |= semantic;
-  }
-
-  if(material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture)
-  {
-    const auto semantic = MaterialDefinition::SPECULAR_COLOR;
-    matDef.mTextureStages.push_back({semantic, ConvertTextureInfo(material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture, context, getTextureMetaData(imageMetaData, material.mMaterialExtensions.mMaterialSpecular.mSpecularColorTexture))});
-    matDef.mFlags |= semantic;
-  }
-
-  matDef.mDoubleSided = material.mDoubleSided;
-
-  outMaterials.emplace_back(std::move(matDef), TextureSet());
-}
-
-void ConvertMaterials(const gt::Document& doc, ConversionContext& context)
-{
-  auto& imageMetaData = context.mOutput.mSceneMetadata.mImageMetadata;
-
-  auto& outMaterials = context.mOutput.mResources.mMaterials;
-  outMaterials.reserve(doc.mMaterials.size());
-
-  for(auto& m : doc.mMaterials)
-  {
-    ConvertMaterial(m, imageMetaData, outMaterials, context);
-  }
-}
-
-MeshDefinition::Accessor ConvertMeshPrimitiveAccessor(const gt::Accessor& acc)
-{
-  DALI_ASSERT_ALWAYS((acc.mBufferView &&
-                      (acc.mBufferView->mByteStride < std::numeric_limits<uint16_t>::max())) ||
-                     (acc.mSparse && !acc.mBufferView));
-
-  DALI_ASSERT_ALWAYS(!acc.mSparse ||
-                     ((acc.mSparse->mIndices.mBufferView && (acc.mSparse->mIndices.mBufferView->mByteStride < std::numeric_limits<uint16_t>::max())) &&
-                      (acc.mSparse->mValues.mBufferView && (acc.mSparse->mValues.mBufferView->mByteStride < std::numeric_limits<uint16_t>::max()))));
-
-  MeshDefinition::SparseBlob sparseBlob;
-  if(acc.mSparse)
-  {
-    const gt::Accessor::Sparse&               sparse  = *acc.mSparse;
-    const gt::ComponentTypedBufferViewClient& indices = sparse.mIndices;
-    const gt::BufferViewClient&               values  = sparse.mValues;
-
-    MeshDefinition::Blob indicesBlob(
-      indices.mBufferView->mByteOffset + indices.mByteOffset,
-      sparse.mCount * indices.GetBytesPerComponent(),
-      static_cast<uint16_t>(indices.mBufferView->mByteStride),
-      static_cast<uint16_t>(indices.GetBytesPerComponent()),
-      {},
-      {});
-    MeshDefinition::Blob valuesBlob(
-      values.mBufferView->mByteOffset + values.mByteOffset,
-      sparse.mCount * acc.GetElementSizeBytes(),
-      static_cast<uint16_t>(values.mBufferView->mByteStride),
-      static_cast<uint16_t>(acc.GetElementSizeBytes()),
-      {},
-      {});
-
-    sparseBlob = std::move(MeshDefinition::SparseBlob(std::move(indicesBlob), std::move(valuesBlob), acc.mSparse->mCount));
-  }
-
-  uint32_t bufferViewOffset = 0u;
-  uint32_t bufferViewStride = 0u;
-  if(acc.mBufferView)
-  {
-    bufferViewOffset = acc.mBufferView->mByteOffset;
-    bufferViewStride = acc.mBufferView->mByteStride;
-  }
-
-  return MeshDefinition::Accessor{
-    std::move(MeshDefinition::Blob{bufferViewOffset + acc.mByteOffset,
-                                   acc.GetBytesLength(),
-                                   static_cast<uint16_t>(bufferViewStride),
-                                   static_cast<uint16_t>(acc.GetElementSizeBytes()),
-                                   acc.mMin,
-                                   acc.mMax}),
-    std::move(sparseBlob),
-    acc.mBufferView ? acc.mBufferView->mBuffer.GetIndex() : 0};
-}
-
-void ConvertMeshes(const gt::Document& doc, ConversionContext& context)
-{
-  uint32_t meshCount = 0;
-  context.mMeshIds.reserve(doc.mMeshes.size());
-  for(auto& mesh : doc.mMeshes)
-  {
-    context.mMeshIds.push_back(meshCount);
-    meshCount += mesh.mPrimitives.size();
-  }
-
-  auto& outMeshes = context.mOutput.mResources.mMeshes;
-  outMeshes.reserve(meshCount);
-  for(auto& mesh : doc.mMeshes)
-  {
-    for(auto& primitive : mesh.mPrimitives)
-    {
-      MeshDefinition meshDefinition;
-
-      auto& attribs                 = primitive.mAttributes;
-      meshDefinition.mPrimitiveType = GLTF2_TO_DALI_PRIMITIVES[primitive.mMode];
-
-      auto& accPositions        = *attribs.find(gt::Attribute::POSITION)->second;
-      meshDefinition.mPositions = ConvertMeshPrimitiveAccessor(accPositions);
-      // glTF2 support vector4 tangent for mesh.
-      // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#meshes-overview
-      meshDefinition.mTangentType = Property::VECTOR4;
-
-      const bool needNormalsTangents = accPositions.mType == gt::AccessorType::VEC3;
-      for(auto& am : ATTRIBUTE_MAPPINGS)
-      {
-        auto iFind = attribs.find(am.mType);
-        if(iFind != attribs.end())
-        {
-          auto& accessor = meshDefinition.*(am.mAccessor);
-          accessor       = ConvertMeshPrimitiveAccessor(*iFind->second);
-
-          if(iFind->first == gt::Attribute::JOINTS_0)
-          {
-            meshDefinition.mFlags |= (iFind->second->mComponentType == gt::Component::UNSIGNED_SHORT) * MeshDefinition::U16_JOINT_IDS;
-            meshDefinition.mFlags |= (iFind->second->mComponentType == gt::Component::UNSIGNED_BYTE) * MeshDefinition::U8_JOINT_IDS;
-            DALI_ASSERT_DEBUG(MaskMatch(meshDefinition.mFlags, MeshDefinition::U16_JOINT_IDS) || MaskMatch(meshDefinition.mFlags, MeshDefinition::U8_JOINT_IDS) || iFind->second->mComponentType == gt::Component::FLOAT);
-          }
-        }
-        else if(needNormalsTangents)
-        {
-          switch(am.mType)
-          {
-            case gt::Attribute::NORMAL:
-              meshDefinition.RequestNormals();
-              break;
-
-            case gt::Attribute::TANGENT:
-              meshDefinition.RequestTangents();
-              break;
-
-            default:
-              break;
-          }
-        }
-      }
-
-      if(primitive.mIndices)
-      {
-        meshDefinition.mIndices = ConvertMeshPrimitiveAccessor(*primitive.mIndices);
-        meshDefinition.mFlags |= (primitive.mIndices->mComponentType == gt::Component::UNSIGNED_INT) * MeshDefinition::U32_INDICES;
-        meshDefinition.mFlags |= (primitive.mIndices->mComponentType == gt::Component::UNSIGNED_BYTE) * MeshDefinition::U8_INDICES;
-        DALI_ASSERT_DEBUG(MaskMatch(meshDefinition.mFlags, MeshDefinition::U32_INDICES) || MaskMatch(meshDefinition.mFlags, MeshDefinition::U8_INDICES) || primitive.mIndices->mComponentType == gt::Component::UNSIGNED_SHORT);
-      }
-
-      if(!primitive.mTargets.empty())
-      {
-        meshDefinition.mBlendShapes.reserve(primitive.mTargets.size());
-        meshDefinition.mBlendShapeVersion = BlendShapes::Version::VERSION_2_0;
-        for(const auto& target : primitive.mTargets)
-        {
-          MeshDefinition::BlendShape blendShape;
-
-          auto endIt = target.end();
-          auto it    = target.find(gt::Attribute::POSITION);
-          if(it != endIt)
-          {
-            blendShape.deltas = ConvertMeshPrimitiveAccessor(*it->second);
-          }
-          it = target.find(gt::Attribute::NORMAL);
-          if(it != endIt)
-          {
-            blendShape.normals = ConvertMeshPrimitiveAccessor(*it->second);
-          }
-          it = target.find(gt::Attribute::TANGENT);
-          if(it != endIt)
-          {
-            blendShape.tangents = ConvertMeshPrimitiveAccessor(*it->second);
-          }
-
-          if(!mesh.mWeights.empty())
-          {
-            blendShape.weight = mesh.mWeights[meshDefinition.mBlendShapes.size()];
-          }
-
-          meshDefinition.mBlendShapes.push_back(std::move(blendShape));
-        }
-      }
-
-      outMeshes.push_back({std::move(meshDefinition), MeshGeometry{}});
-    }
-  }
-}
-
-ModelRenderable* MakeModelRenderable(const gt::Mesh::Primitive& prim, ConversionContext& context)
-{
-  auto modelRenderable = new ModelRenderable();
-
-  modelRenderable->mShaderIdx = 0; // TODO: further thought
-
-  auto materialIdx = prim.mMaterial.GetIndex();
-  if(INVALID_INDEX == materialIdx)
-  {
-    // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#default-material
-    if(INVALID_INDEX == context.mDefaultMaterial)
-    {
-      auto& outMaterials       = context.mOutput.mResources.mMaterials;
-      context.mDefaultMaterial = outMaterials.size();
-
-      ConvertMaterial(gt::Material{}, context.mOutput.mSceneMetadata.mImageMetadata, outMaterials, context);
-    }
-
-    materialIdx = context.mDefaultMaterial;
-  }
-
-  modelRenderable->mMaterialIdx = materialIdx;
-
-  return modelRenderable;
-}
-
-void ConvertCamera(const gt::Camera& camera, CameraParameters& camParams)
-{
-  camParams.isPerspective = camera.mType.compare("perspective") == 0;
-  if(camParams.isPerspective)
-  {
-    auto& perspective = camera.mPerspective;
-    if(!Dali::Equals(perspective.mYFov, gltf2::UNDEFINED_FLOAT_VALUE))
-    {
-      camParams.yFovDegree = Degree(Radian(perspective.mYFov));
-    }
-    else
-    {
-      camParams.yFovDegree = Degree(gltf2::UNDEFINED_FLOAT_VALUE);
-    }
-    camParams.zNear = perspective.mZNear;
-    camParams.zFar  = perspective.mZFar;
-    // TODO: yes, we seem to ignore aspectRatio in CameraParameters.
-  }
-  else
-  {
-    auto& ortho = camera.mOrthographic;
-    if(!Dali::Equals(ortho.mYMag, gltf2::UNDEFINED_FLOAT_VALUE) && !Dali::Equals(ortho.mXMag, gltf2::UNDEFINED_FLOAT_VALUE))
-    {
-      camParams.orthographicSize = ortho.mYMag * .5f;
-      camParams.aspectRatio      = ortho.mXMag / ortho.mYMag;
-    }
-    else
-    {
-      camParams.orthographicSize = gltf2::UNDEFINED_FLOAT_VALUE;
-      camParams.aspectRatio      = gltf2::UNDEFINED_FLOAT_VALUE;
-    }
-    camParams.zNear = ortho.mZNear;
-    camParams.zFar  = ortho.mZFar;
-  }
-}
-
-void ConvertNode(gt::Node const& node, const Index gltfIdx, Index parentIdx, ConversionContext& context, bool isMRendererModel)
-{
-  auto& output    = context.mOutput;
-  auto& scene     = output.mScene;
-  auto& resources = output.mResources;
-
-  const auto idx      = scene.GetNodeCount();
-  auto       weakNode = scene.AddNode([&]() {
-    std::unique_ptr<NodeDefinition> nodeDef{new NodeDefinition()};
-
-    nodeDef->mParentIdx = parentIdx;
-    nodeDef->mName      = node.mName;
-    if(nodeDef->mName.empty())
-    {
-      // TODO: Production quality generation of unique names.
-      nodeDef->mName = std::to_string(reinterpret_cast<uintptr_t>(nodeDef.get()));
-    }
-
-    if(!node.mSkin) // Nodes with skinned meshes are not supposed to have local transforms.
-    {
-      nodeDef->mPosition    = node.mTranslation;
-      nodeDef->mOrientation = node.mRotation;
-      nodeDef->mScale       = node.mScale;
-
-      if(isMRendererModel && node.mName == ROOT_NODE_NAME && node.mScale == SCALE_TO_ADJUST)
-      {
-        nodeDef->mScale *= 0.01f;
-      }
-    }
-
-    return nodeDef;
-  }());
-  if(!weakNode)
-  {
-    ExceptionFlinger(ASSERT_LOCATION) << "Node name '" << node.mName << "' is not unique; scene is invalid.";
-  }
-
-  context.mNodeIndices.RegisterMapping(gltfIdx, idx);
-
-  Index skeletonIdx = node.mSkin ? node.mSkin.GetIndex() : INVALID_INDEX;
-  if(node.mMesh)
-  {
-    auto&    mesh           = *node.mMesh;
-    uint32_t primitiveCount = mesh.mPrimitives.size();
-    auto     meshIdx        = context.mMeshIds[node.mMesh.GetIndex()];
-    weakNode->mRenderables.reserve(primitiveCount);
-    for(uint32_t i = 0; i < primitiveCount; ++i)
-    {
-      std::unique_ptr<NodeDefinition::Renderable> renderable;
-      auto                                        modelRenderable = MakeModelRenderable(mesh.mPrimitives[i], context);
-      modelRenderable->mMeshIdx                                   = meshIdx + i;
-
-      DALI_ASSERT_DEBUG(resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx == INVALID_INDEX ||
-                        resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx == skeletonIdx);
-      resources.mMeshes[modelRenderable->mMeshIdx].first.mSkeletonIdx = skeletonIdx;
-
-      renderable.reset(modelRenderable);
-      weakNode->mRenderables.push_back(std::move(renderable));
-    }
-  }
-
-  if(node.mCamera)
-  {
-    CameraParameters camParams;
-    ConvertCamera(*node.mCamera, camParams);
-
-    camParams.matrix.SetTransformComponents(node.mScale, node.mRotation, node.mTranslation);
-    output.mCameraParameters.push_back(camParams);
-  }
-
-  for(auto& n : node.mChildren)
-  {
-    ConvertNode(*n, n.GetIndex(), idx, context, isMRendererModel);
-  }
-}
-
-void ConvertSceneNodes(const gt::Scene& scene, ConversionContext& context, bool isMRendererModel)
-{
-  auto& outScene = context.mOutput.mScene;
-  Index rootIdx  = outScene.GetNodeCount();
-  switch(scene.mNodes.size())
-  {
-    case 0:
-      break;
-
-    case 1:
-      ConvertNode(*scene.mNodes[0], scene.mNodes[0].GetIndex(), INVALID_INDEX, context, isMRendererModel);
-      outScene.AddRootNode(rootIdx);
-      break;
-
-    default:
-    {
-      std::unique_ptr<NodeDefinition> sceneRoot{new NodeDefinition()};
-      sceneRoot->mName = "GLTF_LOADER_SCENE_ROOT_" + std::to_string(outScene.GetRoots().size());
-
-      outScene.AddNode(std::move(sceneRoot));
-      outScene.AddRootNode(rootIdx);
-
-      for(auto& n : scene.mNodes)
-      {
-        ConvertNode(*n, n.GetIndex(), rootIdx, context, isMRendererModel);
-      }
-      break;
-    }
-  }
-}
-
-void ConvertNodes(const gt::Document& doc, ConversionContext& context, bool isMRendererModel)
-{
-  if(!doc.mScenes.empty())
-  {
-    uint32_t rootSceneIndex = 0u;
-    if(doc.mScene)
-    {
-      rootSceneIndex = doc.mScene.GetIndex();
-    }
-    ConvertSceneNodes(doc.mScenes[rootSceneIndex], context, isMRendererModel);
-
-    for(uint32_t i = 0, i1 = rootSceneIndex; i < i1; ++i)
-    {
-      ConvertSceneNodes(doc.mScenes[i], context, isMRendererModel);
-    }
-
-    for(uint32_t i = rootSceneIndex + 1; i < doc.mScenes.size(); ++i)
-    {
-      ConvertSceneNodes(doc.mScenes[i], context, isMRendererModel);
-    }
-  }
-}
-
-template<typename T>
-void LoadDataFromAccessor(ConversionContext& context, uint32_t bufferIndex, Vector<T>& dataBuffer, uint32_t offset, uint32_t size)
-{
-  if(bufferIndex >= context.mOutput.mResources.mBuffers.size())
-  {
-    DALI_LOG_ERROR("Invailid buffer index\n");
-    return;
-  }
-
-  auto& buffer = context.mOutput.mResources.mBuffers[bufferIndex];
-  if(!buffer.IsAvailable())
-  {
-    DALI_LOG_ERROR("Failed to load from buffer stream.\n");
-  }
-  auto& stream = buffer.GetBufferStream();
-  stream.clear();
-  stream.seekg(offset, stream.beg);
-  stream.read(reinterpret_cast<char*>(dataBuffer.Begin()), static_cast<std::streamsize>(static_cast<size_t>(size)));
-}
-
-template<typename T>
-float LoadDataFromAccessors(ConversionContext& context, const gltf2::Accessor& input, const gltf2::Accessor& output, Vector<float>& inputDataBuffer, Vector<T>& outputDataBuffer)
-{
-  inputDataBuffer.Resize(input.mCount);
-  outputDataBuffer.Resize(output.mCount);
-
-  const uint32_t inputDataBufferSize  = input.GetBytesLength();
-  const uint32_t outputDataBufferSize = output.GetBytesLength();
-
-  LoadDataFromAccessor<float>(context, output.mBufferView->mBuffer.GetIndex(), inputDataBuffer, input.mBufferView->mByteOffset + input.mByteOffset, inputDataBufferSize);
-  LoadDataFromAccessor<T>(context, output.mBufferView->mBuffer.GetIndex(), outputDataBuffer, output.mBufferView->mByteOffset + output.mByteOffset, outputDataBufferSize);
-  ApplyAccessorMinMax(input, reinterpret_cast<float*>(inputDataBuffer.begin()));
-  ApplyAccessorMinMax(output, reinterpret_cast<float*>(outputDataBuffer.begin()));
-
-  return inputDataBuffer[input.mCount - 1u];
-}
-
-template<typename T>
-float LoadKeyFrames(ConversionContext& context, const gt::Animation::Channel& channel, KeyFrames& keyFrames, gt::Animation::Channel::Target::Type type)
-{
-  const gltf2::Accessor& input  = *channel.mSampler->mInput;
-  const gltf2::Accessor& output = *channel.mSampler->mOutput;
-
-  Vector<float> inputDataBuffer;
-  Vector<T>     outputDataBuffer;
-
-  const float duration = std::max(LoadDataFromAccessors<T>(context, input, output, inputDataBuffer, outputDataBuffer), AnimationDefinition::MIN_DURATION_SECONDS);
-
-  // Set first frame value as first keyframe (gltf animation spec)
-  if(input.mCount > 0 && !Dali::EqualsZero(inputDataBuffer[0]))
-  {
-    keyFrames.Add(0.0f, outputDataBuffer[0]);
-  }
-
-  for(uint32_t i = 0; i < input.mCount; ++i)
-  {
-    keyFrames.Add(inputDataBuffer[i] / duration, outputDataBuffer[i]);
-  }
-
-  return duration;
-}
-
-float LoadBlendShapeKeyFrames(ConversionContext& context, const gt::Animation::Channel& channel, Index nodeIndex, uint32_t& propertyIndex, std::vector<Dali::Scene3D::Loader::AnimatedProperty>& properties)
-{
-  const gltf2::Accessor& input  = *channel.mSampler->mInput;
-  const gltf2::Accessor& output = *channel.mSampler->mOutput;
-
-  Vector<float> inputDataBuffer;
-  Vector<float> outputDataBuffer;
-
-  const float duration = std::max(LoadDataFromAccessors<float>(context, input, output, inputDataBuffer, outputDataBuffer), AnimationDefinition::MIN_DURATION_SECONDS);
-
-  char        weightNameBuffer[32];
-  auto        prefixSize    = snprintf(weightNameBuffer, sizeof(weightNameBuffer), "%s[", BLEND_SHAPE_WEIGHTS_UNIFORM.c_str());
-  char* const pWeightName   = weightNameBuffer + prefixSize;
-  const auto  remainingSize = sizeof(weightNameBuffer) - prefixSize;
-  for(uint32_t weightIndex = 0u, endWeightIndex = channel.mSampler->mOutput->mCount / channel.mSampler->mInput->mCount; weightIndex < endWeightIndex; ++weightIndex)
-  {
-    AnimatedProperty& animatedProperty = properties[propertyIndex++];
-
-    animatedProperty.mNodeIndex = nodeIndex;
-    snprintf(pWeightName, remainingSize, "%d]", weightIndex);
-    animatedProperty.mPropertyName = std::string(weightNameBuffer);
-
-    animatedProperty.mKeyFrames = KeyFrames::New();
-
-    // Set first frame value as first keyframe (gltf animation spec)
-    if(input.mCount > 0 && !Dali::EqualsZero(inputDataBuffer[0]))
-    {
-      animatedProperty.mKeyFrames.Add(0.0f, outputDataBuffer[weightIndex]);
-    }
-
-    for(uint32_t i = 0; i < input.mCount; ++i)
-    {
-      animatedProperty.mKeyFrames.Add(inputDataBuffer[i] / duration, outputDataBuffer[i * endWeightIndex + weightIndex]);
-    }
-
-    animatedProperty.mTimePeriod = {0.f, duration};
-  }
-
-  return duration;
-}
-
-void ConvertAnimations(const gt::Document& doc, ConversionContext& context)
-{
-  auto& output = context.mOutput;
-
-  output.mAnimationDefinitions.reserve(output.mAnimationDefinitions.size() + doc.mAnimations.size());
-
-  for(const auto& animation : doc.mAnimations)
-  {
-    AnimationDefinition animationDef;
-
-    if(!animation.mName.empty())
-    {
-      animationDef.mName = animation.mName;
-    }
-
-    uint32_t numberOfProperties = 0u;
-    for(const auto& channel : animation.mChannels)
-    {
-      if(channel.mTarget.mPath == gt::Animation::Channel::Target::WEIGHTS)
-      {
-        numberOfProperties += channel.mSampler->mOutput->mCount / channel.mSampler->mInput->mCount;
-      }
-      else
-      {
-        numberOfProperties++;
-      }
-    }
-    animationDef.mProperties.resize(numberOfProperties);
-
-    Index propertyIndex = 0u;
-    for(const auto& channel : animation.mChannels)
-    {
-      Index nodeIndex = context.mNodeIndices.GetRuntimeId(channel.mTarget.mNode.GetIndex());
-      float duration  = 0.f;
-
-      switch(channel.mTarget.mPath)
-      {
-        case gt::Animation::Channel::Target::TRANSLATION:
-        {
-          AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex];
-
-          animatedProperty.mNodeIndex    = nodeIndex;
-          animatedProperty.mPropertyName = POSITION_PROPERTY;
-
-          animatedProperty.mKeyFrames = KeyFrames::New();
-          duration                    = LoadKeyFrames<Vector3>(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath);
-
-          animatedProperty.mTimePeriod = {0.f, duration};
-          break;
-        }
-        case gt::Animation::Channel::Target::ROTATION:
-        {
-          AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex];
-
-          animatedProperty.mNodeIndex    = nodeIndex;
-          animatedProperty.mPropertyName = ORIENTATION_PROPERTY;
-
-          animatedProperty.mKeyFrames = KeyFrames::New();
-          duration                    = LoadKeyFrames<Quaternion>(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath);
-
-          animatedProperty.mTimePeriod = {0.f, duration};
-          break;
-        }
-        case gt::Animation::Channel::Target::SCALE:
-        {
-          AnimatedProperty& animatedProperty = animationDef.mProperties[propertyIndex];
-
-          animatedProperty.mNodeIndex    = nodeIndex;
-          animatedProperty.mPropertyName = SCALE_PROPERTY;
-
-          animatedProperty.mKeyFrames = KeyFrames::New();
-          duration                    = LoadKeyFrames<Vector3>(context, channel, animatedProperty.mKeyFrames, channel.mTarget.mPath);
-
-          animatedProperty.mTimePeriod = {0.f, duration};
-          break;
-        }
-        case gt::Animation::Channel::Target::WEIGHTS:
-        {
-          duration = LoadBlendShapeKeyFrames(context, channel, nodeIndex, propertyIndex, animationDef.mProperties);
-
-          break;
-        }
-        default:
-        {
-          // nothing to animate.
-          break;
-        }
-      }
-
-      animationDef.mDuration = std::max(duration, animationDef.mDuration);
-
-      ++propertyIndex;
-    }
-
-    output.mAnimationDefinitions.push_back(std::move(animationDef));
-  }
-}
-
-void ProcessSkins(const gt::Document& doc, ConversionContext& context)
-{
-  // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skininversebindmatrices
-  // If an inverseBindMatrices accessor was provided, we'll load the joint data from the buffer,
-  // otherwise we'll set identity matrices for inverse bind pose.
-  struct IInverseBindMatrixProvider
-  {
-    virtual ~IInverseBindMatrixProvider()
-    {
-    }
-    virtual void Provide(Matrix& ibm) = 0;
-  };
-
-  struct InverseBindMatrixAccessor : public IInverseBindMatrixProvider
-  {
-    std::istream&  mStream;
-    const uint32_t mElementSizeBytes;
-
-    InverseBindMatrixAccessor(const gt::Accessor& accessor, ConversionContext& context)
-    : mStream(context.mOutput.mResources.mBuffers[accessor.mBufferView->mBuffer.GetIndex()].GetBufferStream()),
-      mElementSizeBytes(accessor.GetElementSizeBytes())
-    {
-      DALI_ASSERT_DEBUG(accessor.mType == gt::AccessorType::MAT4 && accessor.mComponentType == gt::Component::FLOAT);
-
-      if(!mStream.rdbuf()->in_avail())
-      {
-        DALI_LOG_ERROR("Failed to load from stream\n");
-      }
-      mStream.clear();
-      mStream.seekg(accessor.mBufferView->mByteOffset + accessor.mByteOffset, mStream.beg);
-    }
-
-    virtual void Provide(Matrix& ibm) override
-    {
-      DALI_ASSERT_ALWAYS(mStream.read(reinterpret_cast<char*>(ibm.AsFloat()), static_cast<std::streamsize>(static_cast<size_t>(mElementSizeBytes))));
-    }
-  };
-
-  struct DefaultInverseBindMatrixProvider : public IInverseBindMatrixProvider
-  {
-    virtual void Provide(Matrix& ibm) override
-    {
-      ibm = Matrix::IDENTITY;
-    }
-  };
-
-  auto& resources = context.mOutput.mResources;
-  resources.mSkeletons.reserve(doc.mSkins.size());
-
-  for(auto& skin : doc.mSkins)
-  {
-    std::unique_ptr<IInverseBindMatrixProvider> ibmProvider;
-    if(skin.mInverseBindMatrices)
-    {
-      ibmProvider.reset(new InverseBindMatrixAccessor(*skin.mInverseBindMatrices, context));
-    }
-    else
-    {
-      ibmProvider.reset(new DefaultInverseBindMatrixProvider());
-    }
-
-    SkeletonDefinition skeleton;
-    if(skin.mSkeleton.GetIndex() != INVALID_INDEX)
-    {
-      skeleton.mRootNodeIdx = context.mNodeIndices.GetRuntimeId(skin.mSkeleton.GetIndex());
-    }
-
-    skeleton.mJoints.resize(skin.mJoints.size());
-    auto iJoint = skeleton.mJoints.begin();
-    for(auto& joint : skin.mJoints)
-    {
-      iJoint->mNodeIdx = context.mNodeIndices.GetRuntimeId(joint.GetIndex());
-
-      ibmProvider->Provide(iJoint->mInverseBindMatrix);
-
-      ++iJoint;
-    }
-
-    resources.mSkeletons.push_back(std::move(skeleton));
-  }
-}
-
-void ProduceShaders(ShaderDefinitionFactory& shaderFactory, SceneDefinition& scene)
-{
-  uint32_t nodeCount = scene.GetNodeCount();
-  for(uint32_t i = 0; i < nodeCount; ++i)
-  {
-    auto nodeDef = scene.GetNode(i);
-    for(auto& renderable : nodeDef->mRenderables)
-    {
-      if(shaderFactory.ProduceShader(*renderable) == INVALID_INDEX)
-      {
-        DALI_LOG_ERROR("Fail to produce shader\n");
-      }
-    }
-  }
-}
-
-void SetObjectReaders()
-{
-  js::SetObjectReader(BUFFER_READER);
-  js::SetObjectReader(BUFFER_VIEW_READER);
-  js::SetObjectReader(BUFFER_VIEW_CLIENT_READER);
-  js::SetObjectReader(COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER);
-  js::SetObjectReader(ACCESSOR_SPARSE_READER);
-  js::SetObjectReader(ACCESSOR_READER);
-  js::SetObjectReader(IMAGE_READER);
-  js::SetObjectReader(SAMPLER_READER);
-  js::SetObjectReader(TEXURE_READER);
-  js::SetObjectReader(TEXURE_INFO_READER);
-  js::SetObjectReader(MATERIAL_PBR_READER);
-  js::SetObjectReader(MATERIAL_SPECULAR_READER);
-  js::SetObjectReader(MATERIAL_IOR_READER);
-  js::SetObjectReader(MATERIAL_EXTENSION_READER);
-  js::SetObjectReader(MATERIAL_READER);
-  js::SetObjectReader(MESH_PRIMITIVE_READER);
-  js::SetObjectReader(MESH_READER);
-  js::SetObjectReader(SKIN_READER);
-  js::SetObjectReader(CAMERA_PERSPECTIVE_READER);
-  js::SetObjectReader(CAMERA_ORTHOGRAPHIC_READER);
-  js::SetObjectReader(CAMERA_READER);
-  js::SetObjectReader(NODE_READER);
-  js::SetObjectReader(ANIMATION_SAMPLER_READER);
-  js::SetObjectReader(ANIMATION_TARGET_READER);
-  js::SetObjectReader(ANIMATION_CHANNEL_READER);
-  js::SetObjectReader(ANIMATION_READER);
-  js::SetObjectReader(SCENE_READER);
-}
-
-void SetDefaultEnvironmentMap(const gt::Document& doc, ConversionContext& context)
-{
-  EnvironmentDefinition envDef;
-  envDef.mUseBrdfTexture = true;
-  envDef.mIblIntensity   = Scene3D::Loader::EnvironmentDefinition::GetDefaultIntensity();
-  context.mOutput.mResources.mEnvironmentMaps.push_back({std::move(envDef), EnvironmentDefinition::Textures()});
-}
-
-} // namespace
-
-void InitializeGltfLoader()
-{
-  // Set ObjectReader only once (for all gltf loading).
-  static bool setObjectReadersRequired = true;
-  {
-    Mutex::ScopedLock lock(gInitializeMutex);
-    if(setObjectReadersRequired)
-    {
-      // NOTE: only referencing own, anonymous namespace, const objects; the pointers will never need to change.
-      SetObjectReaders();
-      setObjectReadersRequired = false;
-    }
-  }
-}
-
-void LoadGltfScene(const std::string& url, ShaderDefinitionFactory& shaderFactory, LoadResult& params)
-{
-  bool failed = false;
-  auto js     = LoadTextFile(url.c_str(), &failed);
-  if(failed)
-  {
-    throw std::runtime_error("Failed to load " + url);
-  }
-
-  json::unique_ptr root(json_parse(js.c_str(), js.size()));
-  if(!root)
-  {
-    throw std::runtime_error("Failed to parse " + url);
-  }
-
-  gt::Document doc;
-
-  auto& rootObj = js::Cast<json_object_s>(*root);
-  auto  jsAsset = js::FindObjectChild("asset", rootObj);
-
-  auto jsAssetVersion = js::FindObjectChild("version", js::Cast<json_object_s>(*jsAsset));
-  if(jsAssetVersion)
-  {
-    doc.mAsset.mVersion = js::Read::StringView(*jsAssetVersion);
-  }
-
-  bool isMRendererModel(false);
-  auto jsAssetGenerator = js::FindObjectChild("generator", js::Cast<json_object_s>(*jsAsset));
-  if(jsAssetGenerator)
-  {
-    doc.mAsset.mGenerator = js::Read::StringView(*jsAssetGenerator);
-    isMRendererModel      = (doc.mAsset.mGenerator.find(MRENDERER_MODEL_IDENTIFICATION) != std::string_view::npos);
-  }
-
-  {
-    Mutex::ScopedLock lock(gReadMutex);
-    gt::SetRefReaderObject(doc);
-    DOCUMENT_READER.Read(rootObj, doc);
-  }
-
-  auto              path = url.substr(0, url.rfind('/') + 1);
-  ConversionContext context{params, path, INVALID_INDEX};
-
-  ConvertBuffers(doc, context);
-  ConvertMaterials(doc, context);
-  ConvertMeshes(doc, context);
-  ConvertNodes(doc, context, isMRendererModel);
-  ConvertAnimations(doc, context);
-  ProcessSkins(doc, context);
-  ProduceShaders(shaderFactory, params.mScene);
-  params.mScene.EnsureUniqueSkinningShaderInstances(params.mResources);
-
-  // Set Default Environment map
-  SetDefaultEnvironmentMap(doc, context);
-}
-
-} // namespace Loader
-} // namespace Scene3D
-} // namespace Dali
diff --git a/dali-scene3d/public-api/loader/gltf2-loader.h b/dali-scene3d/public-api/loader/gltf2-loader.h
deleted file mode 100644 (file)
index dc7becf..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-#ifndef DALI_SCENE3D_LOADER_GLTF2_LOADER_H
-#define DALI_SCENE3D_LOADER_GLTF2_LOADER_H
-/*
- * Copyright (c) 2022 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"
-
-// EXTERNAL INCLUDES
-#include <string>
-
-namespace Dali
-{
-namespace Scene3D
-{
-namespace Loader
-{
-struct CameraParameters;
-struct LoadResult;
-class ShaderDefinitionFactory;
-
-/**
- * @brief Initialize glTF Loader.
- * @note This method should be called once before LoadGltfScene() is called.
- */
-DALI_SCENE3D_API void InitializeGltfLoader();
-
-/**
- * @brief Loads the scene from the glTF file located at @a url, storing the results in @a params.
- * @note Will throw std::runtime_error for JSON entities with types mismatching expectations, carrying
- *  invalid values, or I/O errors.
- * @note InitializeGltfLoader() should be called once before this function is called.
- */
-DALI_SCENE3D_API void LoadGltfScene(const std::string& url, ShaderDefinitionFactory& shaderFactory, LoadResult& params);
-
-} // namespace Loader
-} // namespace Scene3D
-} // namespace Dali
-
-#endif //DALI_SCENE3D_LOADER_GLTF2_LOADER_H
diff --git a/dali-scene3d/public-api/loader/model-loader.cpp b/dali-scene3d/public-api/loader/model-loader.cpp
new file mode 100644 (file)
index 0000000..5ca930f
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ *
+ */
+
+// FILE HEADER
+#include <dali-scene3d/public-api/loader/model-loader.h>
+
+// EXTERNAL INCLUDES
+#include <dali/integration-api/debug.h>
+#include <filesystem>
+#include <memory>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/internal/loader/dli-loader-impl.h>
+#include <dali-scene3d/internal/loader/gltf2-loader-impl.h>
+#include <dali-scene3d/internal/loader/model-loader-impl.h>
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Loader
+{
+namespace
+{
+static constexpr std::string_view OBJ_EXTENSION      = ".obj";
+static constexpr std::string_view GLTF_EXTENSION     = ".gltf";
+static constexpr std::string_view DLI_EXTENSION      = ".dli";
+static constexpr std::string_view METADATA_EXTENSION = "metadata";
+} // namespace
+
+ModelLoader::ModelLoader(const std::string& modelUrl, const std::string& resourceDirectoryUrl, Dali::Scene3D::Loader::LoadResult& loadResult)
+: mModelUrl(modelUrl),
+  mResourceDirectoryUrl(resourceDirectoryUrl),
+  mLoadResult(loadResult)
+{
+  CreateModelLoader();
+}
+
+bool ModelLoader::LoadModel(Dali::Scene3D::Loader::ResourceBundle::PathProvider& pathProvider, bool loadOnlyRawResource)
+{
+  if(!mImpl)
+  {
+    return false;
+  }
+
+  bool loadSucceeded = false;
+
+  mLoadResult.mAnimationDefinitions.clear();
+  std::filesystem::path metaDataUrl(mModelUrl);
+  metaDataUrl.replace_extension(METADATA_EXTENSION.data());
+
+  Dali::Scene3D::Loader::LoadSceneMetadata(metaDataUrl.c_str(), mLoadResult.mSceneMetadata);
+  loadSucceeded = mImpl->LoadModel(mModelUrl, mLoadResult);
+  LoadResource(pathProvider, loadOnlyRawResource);
+
+  return loadSucceeded;
+}
+
+void ModelLoader::SetInputParameter(InputParameter& inputParameter)
+{
+  mImpl->SetInputParameter(inputParameter);
+}
+
+Dali::Scene3D::Loader::SceneDefinition& ModelLoader::GetScene()
+{
+  return mLoadResult.mScene;
+}
+
+Dali::Scene3D::Loader::ResourceBundle& ModelLoader::GetResources()
+{
+  return mLoadResult.mResources;
+}
+
+std::vector<Dali::Scene3D::Loader::AnimationDefinition>& ModelLoader::GetAnimations()
+{
+  return mLoadResult.mAnimationDefinitions;
+}
+
+std::vector<Dali::Scene3D::Loader::CameraParameters>& ModelLoader::GetCameras()
+{
+  return mLoadResult.mCameraParameters;
+}
+
+Dali::Scene3D::Loader::Customization::Choices& ModelLoader::GetResourceChoices()
+{
+  return mResourceChoices;
+}
+
+void ModelLoader::CreateModelLoader()
+{
+  std::filesystem::path modelPath(mModelUrl);
+  if(mResourceDirectoryUrl.empty())
+  {
+    mResourceDirectoryUrl = std::string(modelPath.parent_path()) + "/";
+  }
+  std::string extension = modelPath.extension();
+  std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
+
+  if(extension == DLI_EXTENSION)
+  {
+    mImpl = std::make_shared<Dali::Scene3D::Loader::Internal::DliLoaderImpl>();
+  }
+  else if(extension == GLTF_EXTENSION)
+  {
+    mImpl = std::make_shared<Dali::Scene3D::Loader::Internal::Gltf2LoaderImpl>();
+  }
+  else
+  {
+    DALI_LOG_ERROR("Not supported model format : %s\n", extension.c_str());
+  }
+}
+
+void ModelLoader::LoadResource(Dali::Scene3D::Loader::ResourceBundle::PathProvider& pathProvider, bool loadOnlyRawResource)
+{
+  if(GetResources().mRawResourcesLoaded && loadOnlyRawResource)
+  {
+    return;
+  }
+
+  Dali::Scene3D::Loader::ResourceRefCounts resourceRefCount = std::move(mLoadResult.mResources.CreateRefCounter());
+  for(auto iRoot : GetScene().GetRoots())
+  {
+    GetScene().CountResourceRefs(iRoot, mResourceChoices, resourceRefCount);
+  }
+
+  GetResources().mReferenceCounts = std::move(resourceRefCount);
+  GetResources().CountEnvironmentReferences();
+
+  if(loadOnlyRawResource)
+  {
+    GetResources().LoadRawResources(pathProvider);
+  }
+  else
+  {
+    GetResources().LoadResources(pathProvider);
+  }
+}
+
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
diff --git a/dali-scene3d/public-api/loader/model-loader.h b/dali-scene3d/public-api/loader/model-loader.h
new file mode 100644 (file)
index 0000000..7cec995
--- /dev/null
@@ -0,0 +1,126 @@
+#ifndef DALI_SCENE3D_LOADER_MODEL_LOADER_H
+#define DALI_SCENE3D_LOADER_MODEL_LOADER_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/scene-definition.h>
+
+// EXTERNAL INCLUDES
+#include <string>
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Loader
+{
+namespace Internal
+{
+class ModelLoaderImpl;
+}
+
+class DALI_SCENE3D_API ModelLoader
+{
+public:
+  class InputParameter
+  {
+  };
+
+  /**
+   * @brief ModelLoader Constructor.
+   * @param[in] modelUrl Url of the model file to be loaded
+   * @param[in] resourceDirectoryUrl Url of directory that contains resources.
+   * @param[out] loadResult Loaded result that includes scene tree and resources.
+   */
+  ModelLoader(const std::string& modelUrl, const std::string& resourceDirectoryUrl, Dali::Scene3D::Loader::LoadResult& loadResult);
+
+  /**
+   * @brief Request to load model from model url.
+   * @param[in] pathProvider Path provider that defines resource paths.
+   * @param[in] loadOnlyRawResource If true, load Raw resource only, and do not create Dali::Handle
+   * If false, this loader load Raw resource and create Dali::Handle too.
+   * Default value is false;
+   * @return True if model loading is successfully finished.
+   */
+  bool LoadModel(Dali::Scene3D::Loader::ResourceBundle::PathProvider& pathProvider, bool loadOnlyRawResource = false);
+
+  /**
+   * @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 that can be used in loading time.
+   */
+  void SetInputParameter(InputParameter& inputParameter);
+
+  /**
+   * @brief Retrieves loaded scene
+   * @return SceneDefinition that is loaded from file
+   */
+  Dali::Scene3D::Loader::SceneDefinition& GetScene();
+
+  /**
+   * @brief Retrieves resource bundle that includes resource information
+   * @return ResourceBundle for model resources
+   */
+  Dali::Scene3D::Loader::ResourceBundle& GetResources();
+
+  /**
+   * @brief Retrieves loaded AnimationDefinition
+   * @return AnimationDefinition that is loaded from file
+   */
+  std::vector<Dali::Scene3D::Loader::AnimationDefinition>& GetAnimations();
+
+  /**
+   * @brief Retrieves loaded CameraParameters
+   * @return CameraParameters list that is loaded from file
+   */
+  std::vector<Dali::Scene3D::Loader::CameraParameters>& GetCameras();
+
+  /**
+   * @brief Retrieves ResourceChoices
+   * @return Choices for loaded Resources
+   */
+  Dali::Scene3D::Loader::Customization::Choices& GetResourceChoices();
+
+private:
+  /**
+   * @brief Create model loader for each file format.
+   */
+  void CreateModelLoader();
+
+  /**
+   * @brief Load resource of the model.
+   */
+  void LoadResource(Dali::Scene3D::Loader::ResourceBundle::PathProvider& pathProvider, bool loadOnlyRawResource);
+
+private:
+  std::string mModelUrl;
+  std::string mResourceDirectoryUrl;
+
+  Dali::Scene3D::Loader::LoadResult             mLoadResult;
+  Dali::Scene3D::Loader::Customization::Choices mResourceChoices;
+
+  std::shared_ptr<Internal::ModelLoaderImpl> mImpl;
+};
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
+
+#endif // DALI_SCENE3D_LOADER_MODEL_LOADER_H
index 98c8de66cea4289c8b9332057414f5bb173da050..c414c1f8beefbc53678e84d9d38530f6aa0fce8f 100644 (file)
@@ -67,11 +67,11 @@ ResourceRefCounts ResourceBundle::CreateRefCounter() const
   return refCounts;
 }
 
-void ResourceBundle::CountEnvironmentReferences(ResourceRefCounts& refCounts) const
+void ResourceBundle::CountEnvironmentReferences()
 {
-  auto& environmentRefCounts = refCounts[ResourceType::Environment];
+  auto& environmentRefCounts = mReferenceCounts[ResourceType::Environment];
 
-  const auto& materialRefs = refCounts[ResourceType::Material];
+  const auto& materialRefs = mReferenceCounts[ResourceType::Material];
   for(uint32_t i = 0, iEnd = materialRefs.Size(); i != iEnd; ++i)
   {
     if(materialRefs[i] > 0)
@@ -81,7 +81,7 @@ void ResourceBundle::CountEnvironmentReferences(ResourceRefCounts& refCounts) co
   }
 }
 
-void ResourceBundle::LoadResources(const ResourceRefCounts& refCounts, PathProvider pathProvider, Options::Type options)
+void ResourceBundle::LoadResources(PathProvider pathProvider, Options::Type options)
 {
   mRawResourcesLoading = true;
   mResourcesGenerating = true;
@@ -89,7 +89,7 @@ void ResourceBundle::LoadResources(const ResourceRefCounts& refCounts, PathProvi
   const auto kForceLoad  = MaskMatch(options, Options::ForceReload);
   const auto kKeepUnused = MaskMatch(options, Options::KeepUnused);
 
-  const auto& refCountEnvMaps  = refCounts[ResourceType::Environment];
+  const auto& refCountEnvMaps  = mReferenceCounts[ResourceType::Environment];
   auto        environmentsPath = pathProvider(ResourceType::Environment);
   for(uint32_t i = 0, iEnd = refCountEnvMaps.Size(); i != iEnd; ++i)
   {
@@ -107,7 +107,7 @@ void ResourceBundle::LoadResources(const ResourceRefCounts& refCounts, PathProvi
     }
   }
 
-  const auto& refCountShaders = refCounts[ResourceType::Shader];
+  const auto& refCountShaders = mReferenceCounts[ResourceType::Shader];
   auto        shadersPath     = pathProvider(ResourceType::Shader);
   for(uint32_t i = 0, iEnd = refCountShaders.Size(); i != iEnd; ++i)
   {
@@ -124,7 +124,7 @@ void ResourceBundle::LoadResources(const ResourceRefCounts& refCounts, PathProvi
     }
   }
 
-  const auto& refCountMeshes = refCounts[ResourceType::Mesh];
+  const auto& refCountMeshes = mReferenceCounts[ResourceType::Mesh];
   auto        modelsPath     = pathProvider(ResourceType::Mesh);
   for(uint32_t i = 0, iEnd = refCountMeshes.Size(); i != iEnd; ++i)
   {
@@ -141,7 +141,7 @@ void ResourceBundle::LoadResources(const ResourceRefCounts& refCounts, PathProvi
     }
   }
 
-  const auto& refCountMaterials = refCounts[ResourceType::Material];
+  const auto& refCountMaterials = mReferenceCounts[ResourceType::Material];
   auto        imagesPath        = pathProvider(ResourceType::Material);
   for(uint32_t i = 0, iEnd = refCountMaterials.Size(); i != iEnd; ++i)
   {
@@ -165,7 +165,7 @@ void ResourceBundle::LoadResources(const ResourceRefCounts& refCounts, PathProvi
   mResourcesGenerated = true;
 }
 
-void ResourceBundle::LoadRawResources(const ResourceRefCounts& refCounts, PathProvider pathProvider, Options::Type options)
+void ResourceBundle::LoadRawResources(PathProvider pathProvider, Options::Type options)
 {
   const auto kForceLoad = MaskMatch(options, Options::ForceReload);
 
@@ -173,7 +173,7 @@ void ResourceBundle::LoadRawResources(const ResourceRefCounts& refCounts, PathPr
   {
     mRawResourcesLoading = true;
 
-    const auto& refCountEnvMaps  = refCounts[ResourceType::Environment];
+    const auto& refCountEnvMaps  = mReferenceCounts[ResourceType::Environment];
     auto        environmentsPath = pathProvider(ResourceType::Environment);
     for(uint32_t i = 0, iEnd = refCountEnvMaps.Size(); i != iEnd; ++i)
     {
@@ -185,7 +185,7 @@ void ResourceBundle::LoadRawResources(const ResourceRefCounts& refCounts, PathPr
       }
     }
 
-    const auto& refCountShaders = refCounts[ResourceType::Shader];
+    const auto& refCountShaders = mReferenceCounts[ResourceType::Shader];
     auto        shadersPath     = pathProvider(ResourceType::Shader);
     for(uint32_t i = 0, iEnd = refCountShaders.Size(); i != iEnd; ++i)
     {
@@ -197,7 +197,7 @@ void ResourceBundle::LoadRawResources(const ResourceRefCounts& refCounts, PathPr
       }
     }
 
-    const auto& refCountMeshes = refCounts[ResourceType::Mesh];
+    const auto& refCountMeshes = mReferenceCounts[ResourceType::Mesh];
     auto        modelsPath     = pathProvider(ResourceType::Mesh);
     for(uint32_t i = 0, iEnd = refCountMeshes.Size(); i != iEnd; ++i)
     {
@@ -209,7 +209,7 @@ void ResourceBundle::LoadRawResources(const ResourceRefCounts& refCounts, PathPr
       }
     }
 
-    const auto& refCountMaterials = refCounts[ResourceType::Material];
+    const auto& refCountMaterials = mReferenceCounts[ResourceType::Material];
     auto        imagesPath        = pathProvider(ResourceType::Material);
     for(uint32_t i = 0, iEnd = refCountMaterials.Size(); i != iEnd; ++i)
     {
@@ -226,7 +226,7 @@ void ResourceBundle::LoadRawResources(const ResourceRefCounts& refCounts, PathPr
   }
 }
 
-void ResourceBundle::GenerateResources(const ResourceRefCounts& refCounts, Options::Type options)
+void ResourceBundle::GenerateResources(Options::Type options)
 {
   const auto kForceLoad = MaskMatch(options, Options::ForceReload);
 
@@ -236,7 +236,7 @@ void ResourceBundle::GenerateResources(const ResourceRefCounts& refCounts, Optio
     {
       mResourcesGenerating = true;
 
-      const auto& refCountEnvMaps = refCounts[ResourceType::Environment];
+      const auto& refCountEnvMaps = mReferenceCounts[ResourceType::Environment];
       for(uint32_t i = 0, iEnd = refCountEnvMaps.Size(); i != iEnd; ++i)
       {
         auto  refCount = refCountEnvMaps[i];
@@ -255,7 +255,7 @@ void ResourceBundle::GenerateResources(const ResourceRefCounts& refCounts, Optio
         }
       }
 
-      const auto& refCountShaders = refCounts[ResourceType::Shader];
+      const auto& refCountShaders = mReferenceCounts[ResourceType::Shader];
       for(uint32_t i = 0, iEnd = refCountShaders.Size(); i != iEnd; ++i)
       {
         auto  refCount = refCountShaders[i];
@@ -273,7 +273,7 @@ void ResourceBundle::GenerateResources(const ResourceRefCounts& refCounts, Optio
         }
       }
 
-      const auto& refCountMeshes = refCounts[ResourceType::Mesh];
+      const auto& refCountMeshes = mReferenceCounts[ResourceType::Mesh];
       for(uint32_t i = 0, iEnd = refCountMeshes.Size(); i != iEnd; ++i)
       {
         auto  refCount = refCountMeshes[i];
@@ -291,7 +291,7 @@ void ResourceBundle::GenerateResources(const ResourceRefCounts& refCounts, Optio
         }
       }
 
-      const auto& refCountMaterials = refCounts[ResourceType::Material];
+      const auto& refCountMaterials = mReferenceCounts[ResourceType::Material];
       for(uint32_t i = 0, iEnd = refCountMaterials.Size(); i != iEnd; ++i)
       {
         auto  refCount  = refCountMaterials[i];
@@ -316,7 +316,7 @@ void ResourceBundle::GenerateResources(const ResourceRefCounts& refCounts, Optio
     {
       mResourcesGenerating = true;
 
-      const auto& refCountShaders = refCounts[ResourceType::Shader];
+      const auto& refCountShaders = mReferenceCounts[ResourceType::Shader];
       for(uint32_t i = 0, iEnd = refCountShaders.Size(); i != iEnd; ++i)
       {
         auto  refCount = refCountShaders[i];
index 95c8609d8ddd974d828b6b924e3d3618b3c31d3f..ee56e70fe335def20a1349e0ef367fceb7be994a 100644 (file)
@@ -103,7 +103,7 @@ public:
    *  count of materials therein, it will calculate the reference count of
    *  environment maps.
    */
-  void CountEnvironmentReferences(ResourceRefCounts& refCounts) const;
+  void CountEnvironmentReferences();
 
   /**
    * @brief Performs the loading of all resources based on their respective
@@ -111,14 +111,12 @@ public:
    * loaded unless we already have a handle to them (OR the ForceReload option was specified).
    * Any handles we have to resources that come in with a zero ref count will be reset,
    * UNLESS the KeepUnused option was specified.
-   * @param[in] refCounts Reference Count that denote how many the resource is used.
    * @param[in] pathProvider path provider for resource data.
    * @param[in] options Option to load resource
    * @note This method creates DALi objects like Dali::Texture, Dali::Geometry, etc.
    */
-  void LoadResources(const ResourceRefCounts& refCounts,
-                     PathProvider             pathProvider,
-                     Options::Type            options = Options::None);
+  void LoadResources(PathProvider  pathProvider,
+                     Options::Type options = Options::None);
 
   /**
    * @brief Loads of all resources based on their respective
@@ -127,29 +125,26 @@ public:
    * Any handles we have to resources that come in with a zero ref count will be reset,
    * UNLESS the KeepUnused option was specified.
    * @note This method don't create any of DALi objects.
-   * @param[in] refCounts Reference Count that denote how many the resource is used.
    * @param[in] pathProvider path provider for resource data.
    * @param[in] options Option to load resource
    * @note This method only loads raw data from resource file, and
    * doesn't create any of DALi objects. GenerateResources() method is required to be called
    * after this method to create DALi objects.
    */
-  void LoadRawResources(const ResourceRefCounts& refCounts,
-                        PathProvider             pathProvider,
-                        Options::Type            options = Options::None);
+  void LoadRawResources(PathProvider  pathProvider,
+                        Options::Type options = Options::None);
 
   /**
    * @brief Generates DALi objects from already loaded Raw Resources.
-   * @param[in] refCounts Reference Count that denote how many the resource is used.
    * @param[in] options Option to load resource
    * @note This method generates DALi objects from raw data that is already
    * loaded by LoadRawResources method. Therefore, LoadRawResources should be called first
    * before this method is called.
    */
-  void GenerateResources(const ResourceRefCounts& refCounts,
-                         Options::Type            options = Options::None);
+  void GenerateResources(Options::Type options = Options::None);
 
 public: // DATA
+  ResourceRefCounts             mReferenceCounts;
   EnvironmentDefinition::Vector mEnvironmentMaps;
   ShaderDefinition::Vector      mShaders;
   MeshDefinition::Vector        mMeshes;