Add glb-loader to load gltf2-binary 71/288871/20
authorseungho baek <sbsh.baek@samsung.com>
Fri, 24 Feb 2023 01:40:25 +0000 (10:40 +0900)
committerseungho baek <sbsh.baek@samsung.com>
Tue, 7 Mar 2023 07:13:32 +0000 (16:13 +0900)
 - extract gltf2-util to share it in gltf2-loader and glb-loader

Change-Id: I048d8903d27fea47a322dcf4d4cc56a16c10acec

15 files changed:
automated-tests/resources/BoxAnimated.glb [new file with mode: 0644]
automated-tests/src/dali-scene3d-internal/CMakeLists.txt
automated-tests/src/dali-scene3d-internal/utc-Dali-GlbLoaderImpl.cpp [new file with mode: 0644]
automated-tests/src/dali-scene3d-internal/utc-Dali-Gltf2LoaderImpl.cpp
dali-scene3d/internal/file.list
dali-scene3d/internal/loader/dli-loader-impl.h
dali-scene3d/internal/loader/glb-loader-impl.cpp [new file with mode: 0644]
dali-scene3d/internal/loader/glb-loader-impl.h [new file with mode: 0644]
dali-scene3d/internal/loader/gltf2-loader-impl.cpp
dali-scene3d/internal/loader/gltf2-loader-impl.h
dali-scene3d/internal/loader/gltf2-util.cpp [new file with mode: 0644]
dali-scene3d/internal/loader/gltf2-util.h [new file with mode: 0644]
dali-scene3d/public-api/loader/buffer-definition.cpp
dali-scene3d/public-api/loader/buffer-definition.h
dali-scene3d/public-api/loader/model-loader.cpp

diff --git a/automated-tests/resources/BoxAnimated.glb b/automated-tests/resources/BoxAnimated.glb
new file mode 100644 (file)
index 0000000..69481ec
Binary files /dev/null and b/automated-tests/resources/BoxAnimated.glb differ
index 0109332..a7a419e 100755 (executable)
@@ -8,6 +8,7 @@ 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-GlbLoaderImpl.cpp
   utc-Dali-Gltf2Asset.cpp
   utc-Dali-Gltf2LoaderImpl.cpp
   utc-Dali-Hash.cpp
diff --git a/automated-tests/src/dali-scene3d-internal/utc-Dali-GlbLoaderImpl.cpp b/automated-tests/src/dali-scene3d-internal/utc-Dali-GlbLoaderImpl.cpp
new file mode 100644 (file)
index 0000000..f3d8a93
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * 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/glb-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::GlbLoaderImpl 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 UtcDaliGlbLoaderFailedToLoad(void)
+{
+  Context ctx;
+
+  DALI_TEST_EQUAL(ctx.loader.LoadModel("non-existent.glb", 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 UtcDaliGlbLoaderFailedToParse(void)
+{
+  Context ctx;
+
+  ShaderDefinitionFactory sdf;
+  sdf.SetResources(ctx.resources);
+
+  DALI_TEST_EQUAL(ctx.loader.LoadModel(TEST_RESOURCE_DIR "/invalid.glb", 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 UtcDaliGlbLoaderSuccess1(void)
+{
+  Context                 ctx;
+  ShaderDefinitionFactory sdf;
+  sdf.SetResources(ctx.resources);
+
+  ctx.loader.LoadModel(TEST_RESOURCE_DIR "/BoxAnimated.glb", ctx.loadResult);
+
+  DALI_TEST_EQUAL(1u, ctx.scene.GetRoots().size());
+  DALI_TEST_EQUAL(5u, 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(1152, ctx.resources.mMeshes[0u].first.mPositions.mBlob.mLength);
+
+  END_TEST;
+}
index 38f8e1e..b305a41 100644 (file)
@@ -55,7 +55,8 @@ namespace
 {
 struct Context
 {
-  ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type) {
+  ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type)
+  {
     return TEST_RESOURCE_DIR "/";
   };
 
@@ -483,7 +484,8 @@ int UtcDaliGltfLoaderSuccessShort(void)
   TestApplication app;
 
   const std::string resourcePath = TEST_RESOURCE_DIR "/";
-  auto              pathProvider = [resourcePath](ResourceType::Value) {
+  auto              pathProvider = [resourcePath](ResourceType::Value)
+  {
     return resourcePath;
   };
 
index f9d8182..7a6c868 100644 (file)
@@ -12,7 +12,9 @@ set(scene3d_src_files ${scene3d_src_files}
        ${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-util.cpp
        ${scene3d_internal_dir}/loader/gltf2-loader-impl.cpp
+       ${scene3d_internal_dir}/loader/glb-loader-impl.cpp
        ${scene3d_internal_dir}/loader/hash.cpp
        ${scene3d_internal_dir}/loader/json-reader.cpp
        ${scene3d_internal_dir}/loader/json-util.cpp
index a68432c..ed0af39 100644 (file)
@@ -28,7 +28,7 @@
 #include <dali-scene3d/public-api/loader/string-callback.h>
 
 // EXTERNAL INCLUDES
-#include "dali/public-api/common/vector-wrapper.h"
+#include <dali/public-api/common/vector-wrapper.h>
 
 namespace Dali
 {
@@ -62,7 +62,7 @@ public:
   void SetErrorCallback(StringCallback onError);
 
   /**
-   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl()
+   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl::LoadMode()
    */
   bool LoadModel(const std::string& uri, Dali::Scene3D::Loader::LoadResult& result) override;
 
diff --git a/dali-scene3d/internal/loader/glb-loader-impl.cpp b/dali-scene3d/internal/loader/glb-loader-impl.cpp
new file mode 100644 (file)
index 0000000..0bfae86
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 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/glb-loader-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/file-stream.h>
+#include <dali/integration-api/debug.h>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/internal/loader/gltf2-util.h>
+#include <dali-scene3d/public-api/loader/load-result.h>
+#include <dali-scene3d/public-api/loader/utils.h>
+
+namespace gt = gltf2;
+namespace js = json;
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Loader
+{
+namespace Internal
+{
+namespace
+{
+static constexpr uint32_t GLB_MAGIC       = 0x46546C67;
+static constexpr uint32_t JSON_CHUNK_TYPE = 0x4E4F534A;
+static constexpr uint32_t DATA_CHUNK_TYPE = 0x004E4942;
+
+struct GlbHeader
+{
+  uint32_t magic;
+  uint32_t version;
+  uint32_t length;
+};
+
+struct ChunkHeader
+{
+  uint32_t chunkLength;
+  uint32_t chunkType;
+};
+
+} // namespace
+
+bool GlbLoaderImpl::LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result)
+{
+  Dali::FileStream fileStream(url, FileStream::READ | FileStream::BINARY);
+  auto&            stream = fileStream.GetStream();
+  if(!stream.rdbuf()->in_avail())
+  {
+    DALI_LOG_ERROR("Load Model file is failed, url : %s\n", url.c_str());
+    return false;
+  }
+
+  GlbHeader glbHeader;
+  stream.clear();
+  stream.seekg(0u, stream.beg);
+  stream.read(reinterpret_cast<char*>(&glbHeader), sizeof(GlbHeader));
+
+  if(glbHeader.magic != GLB_MAGIC)
+  {
+    DALI_LOG_ERROR("Wrong file format, url : %s\n", url.c_str());
+    return false;
+  }
+
+  ChunkHeader jsonChunkHeader;
+  stream.read(reinterpret_cast<char*>(&jsonChunkHeader), sizeof(ChunkHeader));
+
+  if(jsonChunkHeader.chunkType != JSON_CHUNK_TYPE)
+  {
+    DALI_LOG_ERROR("Glb files first chunk is not a json chunk.\n");
+    return false;
+  }
+
+  std::vector<uint8_t> jsonChunkData;
+  jsonChunkData.resize(jsonChunkHeader.chunkLength);
+  stream.read(reinterpret_cast<char*>(&jsonChunkData[0]), jsonChunkHeader.chunkLength);
+  std::string gltfText(jsonChunkData.begin(), jsonChunkData.end());
+
+  uint32_t             binaryChunkOffset = sizeof(GlbHeader) + sizeof(ChunkHeader) + jsonChunkHeader.chunkLength;
+  std::vector<uint8_t> binaryChunkData;
+  if(glbHeader.length > binaryChunkOffset)
+  {
+    ChunkHeader binaryChunkHeader;
+    stream.read(reinterpret_cast<char*>(&binaryChunkHeader), sizeof(ChunkHeader));
+
+    if(binaryChunkHeader.chunkType != DATA_CHUNK_TYPE)
+    {
+      DALI_LOG_ERROR("Glb files has wrong binary chunk data.\n");
+      return false;
+    }
+
+    binaryChunkData.resize(binaryChunkHeader.chunkLength);
+    stream.read(reinterpret_cast<char*>(&binaryChunkData[0]), binaryChunkHeader.chunkLength);
+  }
+
+  json::unique_ptr root(json_parse(gltfText.c_str(), gltfText.size()));
+  if(!root)
+  {
+    DALI_LOG_ERROR("Failed to parse %s\n", url.c_str());
+    return false;
+  }
+
+  gt::Document document;
+
+  bool isMRendererModel(false);
+  if(!Gltf2Util::GenerateDocument(root, document, isMRendererModel))
+  {
+    DALI_LOG_ERROR("Failed to parse %s\n", url.c_str());
+    return false;
+  }
+
+  auto                         path = url.substr(0, url.rfind('/') + 1);
+  Gltf2Util::ConversionContext context{result, path, INVALID_INDEX};
+
+  auto& outBuffers = context.mOutput.mResources.mBuffers;
+  outBuffers.reserve(document.mBuffers.size());
+  if(!binaryChunkData.empty())
+  {
+    BufferDefinition dataBuffer(binaryChunkData);
+    outBuffers.emplace_back(std::move(dataBuffer));
+  }
+
+  Gltf2Util::ConvertGltfToContext(document, context,isMRendererModel);
+
+  return true;
+}
+
+} // namespace Internal
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
diff --git a/dali-scene3d/internal/loader/glb-loader-impl.h b/dali-scene3d/internal/loader/glb-loader-impl.h
new file mode 100644 (file)
index 0000000..4e9607b
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef DALI_SCENE3D_LOADER_GLB_LOADER_IMPL_H
+#define DALI_SCENE3D_LOADER_GLB_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 GlbLoaderImpl : public ModelLoaderImpl
+{
+public:
+
+  /**
+   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl::LoadMode()
+   */
+  bool LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) override;
+};
+
+} // namespace Internal
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
+
+#endif // DALI_SCENE3D_LOADER_GLB_LOADER_IMPL_H
index f6282a8..ae08709 100644 (file)
 
 // 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/internal/loader/gltf2-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-scene3d/public-api/loader/shader-definition-factory.h>
 #include <dali-scene3d/public-api/loader/utils.h>
 
 namespace gt = gltf2;
@@ -43,1464 +37,36 @@ namespace Loader
 {
 namespace Internal
 {
-namespace
-{
-
-const char*   POSITION_PROPERTY("position");
-const char*   ORIENTATION_PROPERTY("orientation");
-const char*   SCALE_PROPERTY("scale");
-const char*   BLEND_SHAPE_WEIGHTS_UNIFORM("uBlendShapeWeight");
-const char*   MRENDERER_MODEL_IDENTIFICATION("M-Renderer");
-const char*   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 js::Reader<gt::Buffer>& GetBufferReader()
-{
-  static 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)));
-  return BUFFER_READER;
-}
-
-const js::Reader<gt::BufferView>& GetBufferViewReader()
-{
-  static 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)));
-  return BUFFER_VIEW_READER;
-}
-
-const js::Reader<gt::BufferViewClient>& GetBufferViewClientReader()
-{
-  static 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)));
-  return BUFFER_VIEW_CLIENT_READER;
-}
-
-const js::Reader<gt::ComponentTypedBufferViewClient>& GetComponentTypedBufferViewClientReader()
-{
-  static 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)));
-  return COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER;
-}
-
-const js::Reader<gt::Accessor::Sparse>& GetAccessorSparseReader()
-{
-  static 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)));
-  return ACCESSOR_SPARSE_READER;
-}
-
-const js::Reader<gt::Accessor>& GetAccessorReader()
-{
-  static 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)));
-  return ACCESSOR_READER;
-}
-
-const js::Reader<gt::Image>& GetImageReader()
-{
-  static 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)));
-  return IMAGE_READER;
-}
-
-const js::Reader<gt::Sampler>& GetSamplerReader()
-{
-  static 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)));
-  return SAMPLER_READER;
-}
-
-const js::Reader<gt::Texture>& GetTextureReader()
-{
-  static 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)));
-  return TEXURE_READER;
-}
-
-const js::Reader<gt::TextureInfo>& GetTextureInfoReader()
-{
-  static 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)));
-  return TEXURE_INFO_READER;
-}
-
-const js::Reader<gt::Material::Pbr>& GetMaterialPbrReader()
-{
-  static 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)));
-  return MATERIAL_PBR_READER;
-}
-
-const js::Reader<gt::MaterialSpecular>& GetMaterialSpecularReader()
-{
-  static 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)));
-  return MATERIAL_SPECULAR_READER;
-}
-
-const js::Reader<gt::MaterialIor>& GetMaterialIorReader()
-{
-  static const auto MATERIAL_IOR_READER = std::move(js::Reader<gt::MaterialIor>()
-                                             .Register(*js::MakeProperty("ior", js::Read::Number<float>, &gt::MaterialIor::mIor)));
-  return MATERIAL_IOR_READER;
-}
-
-const js::Reader<gt::MaterialExtensions>& GetMaterialExtensionsReader()
-{
-  static 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)));
-  return MATERIAL_EXTENSION_READER;
-}
-
-const js::Reader<gt::Material>& GetMaterialReader()
-{
-  static 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)));
-  return MATERIAL_READER;
-}
-
-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 js::Reader<gt::Mesh::Primitive>& GetMeshPrimitiveReader()
-{
-  static 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)));
-  return MESH_PRIMITIVE_READER;
-}
-
-const js::Reader<gt::Mesh>& GetMeshReader()
-{
-  static 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)));
-  return MESH_READER;
-}
-
-const js::Reader<gt::Skin>& GetSkinReader()
-{
-  static 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)));
-  return SKIN_READER;
-}
-
-const js::Reader<gt::Camera::Perspective>& GetCameraPerspectiveReader()
-{
-  static 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
-  return CAMERA_PERSPECTIVE_READER;
-}
-
-const js::Reader<gt::Camera::Orthographic>& GetCameraOrthographicReader()
-{
-  static 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)));
-  return CAMERA_ORTHOGRAPHIC_READER;
-}
-
-const js::Reader<gt::Camera>& GetCameraReader()
-{
-  static 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)));
-  return CAMERA_READER;
-}
-
-const js::Reader<gt::Node>& GetNodeReader()
-{
-  static 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)));
-  return NODE_READER;
-}
-
-const js::Reader<gt::Animation::Sampler>& GetAnimationSamplerReader()
-{
-  static 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)));
-  return ANIMATION_SAMPLER_READER;
-}
-
-const js::Reader<gt::Animation::Channel::Target>& GetAnimationChannelTargetReader()
-{
-  static 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)));
-  return ANIMATION_TARGET_READER;
-}
-
-const js::Reader<gt::Animation::Channel>& GetAnimationChannelReader()
-{
-  static 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)));
-  return ANIMATION_CHANNEL_READER;
-}
-
-const js::Reader<gt::Animation>& GetAnimationReader()
-{
-  static 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)));
-  return ANIMATION_READER;
-}
-
-const js::Reader<gt::Scene>& GetSceneReader()
-{
-  static 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)));
-  return SCENE_READER;
-}
-
-const js::Reader<gt::Document>& GetDocumentReader()
-{
-  static 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)));
-  return DOCUMENT_READER;
-}
-
-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);
-  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(GetBufferReader());
-  js::SetObjectReader(GetBufferViewReader());
-  js::SetObjectReader(GetBufferViewClientReader());
-  js::SetObjectReader(GetComponentTypedBufferViewClientReader());
-  js::SetObjectReader(GetAccessorSparseReader());
-  js::SetObjectReader(GetAccessorReader());
-  js::SetObjectReader(GetImageReader());
-  js::SetObjectReader(GetSamplerReader());
-  js::SetObjectReader(GetTextureReader());
-  js::SetObjectReader(GetTextureInfoReader());
-  js::SetObjectReader(GetMaterialPbrReader());
-  js::SetObjectReader(GetMaterialSpecularReader());
-  js::SetObjectReader(GetMaterialIorReader());
-  js::SetObjectReader(GetMaterialExtensionsReader());
-  js::SetObjectReader(GetMaterialReader());
-  js::SetObjectReader(GetMeshPrimitiveReader());
-  js::SetObjectReader(GetMeshReader());
-  js::SetObjectReader(GetSkinReader());
-  js::SetObjectReader(GetCameraPerspectiveReader());
-  js::SetObjectReader(GetCameraOrthographicReader());
-  js::SetObjectReader(GetCameraReader());
-  js::SetObjectReader(GetNodeReader());
-  js::SetObjectReader(GetAnimationSamplerReader());
-  js::SetObjectReader(GetAnimationChannelTargetReader());
-  js::SetObjectReader(GetAnimationChannelReader());
-  js::SetObjectReader(GetAnimationReader());
-  js::SetObjectReader(GetSceneReader());
-}
-
-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);
+  bool failed   = false;
+  auto gltfText = 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()));
+  json::unique_ptr root(json_parse(gltfText.c_str(), gltfText.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);
-  }
+  gt::Document document;
 
   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();
+  if(!Gltf2Util::GenerateDocument(root, document, isMRendererModel))
   {
-    static Dali::Mutex mReadMutex;
-    Mutex::ScopedLock  lock(mReadMutex);
-    gt::SetRefReaderObject(doc);
-    GetDocumentReader().Read(rootObj, doc);
+    return false;
   }
 
-  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);
+  auto                         path = url.substr(0, url.rfind('/') + 1);
+  Gltf2Util::ConversionContext context{result, path, INVALID_INDEX};
 
-  // Set Default Environment map
-  SetDefaultEnvironmentMap(doc, context);
+  Gltf2Util::ConvertGltfToContext(document, context, isMRendererModel);
 
   return true;
 }
index 2b96608..319d91c 100644 (file)
@@ -39,16 +39,9 @@ class Gltf2LoaderImpl : public ModelLoaderImpl
 public:
 
   /**
-   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl()
+   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl::LoadMode()
    */
   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
diff --git a/dali-scene3d/internal/loader/gltf2-util.cpp b/dali-scene3d/internal/loader/gltf2-util.cpp
new file mode 100644 (file)
index 0000000..15361b7
--- /dev/null
@@ -0,0 +1,1329 @@
+/*
+ * 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/gltf2-util.h>
+
+using namespace Dali::Scene3D::Loader;
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Loader
+{
+namespace Internal
+{
+namespace Gltf2Util
+{
+static constexpr std::string_view MRENDERER_MODEL_IDENTIFICATION = "M-Renderer";
+static constexpr std::string_view POSITION_PROPERTY              = "position";
+static constexpr std::string_view ORIENTATION_PROPERTY           = "orientation";
+static constexpr std::string_view SCALE_PROPERTY                 = "scale";
+static constexpr std::string_view BLEND_SHAPE_WEIGHTS_UNIFORM    = "uBlendShapeWeight";
+static constexpr std::string_view ROOT_NODE_NAME                 = "RootNode";
+static const Vector3              SCALE_TO_ADJUST(100.0f, 100.0f, 100.0f);
+
+static 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.
+
+static struct AttributeMapping
+{
+  gltf2::Attribute::Type      mType;
+  MeshDefinition::Accessor MeshDefinition::*mAccessor;
+  uint16_t                                  mElementSizeRequired;
+} ATTRIBUTE_MAPPINGS[]{
+  {gltf2::Attribute::NORMAL, &MeshDefinition::mNormals, sizeof(Vector3)},
+  {gltf2::Attribute::TANGENT, &MeshDefinition::mTangents, sizeof(Vector3)},
+  {gltf2::Attribute::TEXCOORD_0, &MeshDefinition::mTexCoords, sizeof(Vector2)},
+  {gltf2::Attribute::COLOR_0, &MeshDefinition::mColors, sizeof(Vector4)},
+  {gltf2::Attribute::JOINTS_0, &MeshDefinition::mJoints0, sizeof(Vector4)},
+  {gltf2::Attribute::WEIGHTS_0, &MeshDefinition::mWeights0, sizeof(Vector4)},
+};
+
+std::vector<gltf2::Animation> ReadAnimationArray(const json_value_s& j)
+{
+  auto results = json::Read::Array<gltf2::Animation, json::ObjectReader<gltf2::Animation>::Read>(j);
+
+  for(auto& animation : results)
+  {
+    for(auto& channel : animation.mChannels)
+    {
+      channel.mSampler.UpdateVector(animation.mSamplers);
+    }
+  }
+
+  return results;
+}
+
+void ApplyAccessorMinMax(const gltf2::Accessor& acc, float* values)
+{
+  DALI_ASSERT_ALWAYS(acc.mMax.empty() || gltf2::AccessorType::ElementCount(acc.mType) == acc.mMax.size());
+  DALI_ASSERT_ALWAYS(acc.mMin.empty() || gltf2::AccessorType::ElementCount(acc.mType) == acc.mMin.size());
+  MeshDefinition::Blob::ApplyMinMax(acc.mMin, acc.mMax, acc.mCount, values);
+}
+
+const auto BUFFER_READER = std::move(json::Reader<gltf2::Buffer>()
+                                       .Register(*json::MakeProperty("byteLength", json::Read::Number<uint32_t>, &gltf2::Buffer::mByteLength))
+                                       .Register(*json::MakeProperty("uri", json::Read::StringView, &gltf2::Buffer::mUri)));
+
+const auto BUFFER_VIEW_READER = std::move(json::Reader<gltf2::BufferView>()
+                                            .Register(*json::MakeProperty("buffer", gltf2::RefReader<gltf2::Document>::Read<gltf2::Buffer, &gltf2::Document::mBuffers>, &gltf2::BufferView::mBuffer))
+                                            .Register(*json::MakeProperty("byteOffset", json::Read::Number<uint32_t>, &gltf2::BufferView::mByteOffset))
+                                            .Register(*json::MakeProperty("byteLength", json::Read::Number<uint32_t>, &gltf2::BufferView::mByteLength))
+                                            .Register(*json::MakeProperty("byteStride", json::Read::Number<uint32_t>, &gltf2::BufferView::mByteStride))
+                                            .Register(*json::MakeProperty("target", json::Read::Number<uint32_t>, &gltf2::BufferView::mTarget)));
+
+const auto BUFFER_VIEW_CLIENT_READER = std::move(json::Reader<gltf2::BufferViewClient>()
+                                                   .Register(*json::MakeProperty("bufferView", gltf2::RefReader<gltf2::Document>::Read<gltf2::BufferView, &gltf2::Document::mBufferViews>, &gltf2::BufferViewClient::mBufferView))
+                                                   .Register(*json::MakeProperty("byteOffset", json::Read::Number<uint32_t>, &gltf2::BufferViewClient::mByteOffset)));
+
+const auto COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER = std::move(json::Reader<gltf2::ComponentTypedBufferViewClient>()
+                                                                   .Register(*new json::Property<gltf2::ComponentTypedBufferViewClient, gltf2::Ref<gltf2::BufferView>>("bufferView", gltf2::RefReader<gltf2::Document>::Read<gltf2::BufferView, &gltf2::Document::mBufferViews>, &gltf2::ComponentTypedBufferViewClient::mBufferView))
+                                                                   .Register(*new json::Property<gltf2::ComponentTypedBufferViewClient, uint32_t>("byteOffset", json::Read::Number<uint32_t>, &gltf2::ComponentTypedBufferViewClient::mByteOffset))
+                                                                   .Register(*json::MakeProperty("componentType", json::Read::Enum<gltf2::Component::Type>, &gltf2::ComponentTypedBufferViewClient::mComponentType)));
+
+const auto ACCESSOR_SPARSE_READER = std::move(json::Reader<gltf2::Accessor::Sparse>()
+                                                .Register(*json::MakeProperty("count", json::Read::Number<uint32_t>, &gltf2::Accessor::Sparse::mCount))
+                                                .Register(*json::MakeProperty("indices", json::ObjectReader<gltf2::ComponentTypedBufferViewClient>::Read, &gltf2::Accessor::Sparse::mIndices))
+                                                .Register(*json::MakeProperty("values", json::ObjectReader<gltf2::BufferViewClient>::Read, &gltf2::Accessor::Sparse::mValues)));
+
+const auto ACCESSOR_READER = std::move(json::Reader<gltf2::Accessor>()
+                                         .Register(*new json::Property<gltf2::Accessor, gltf2::Ref<gltf2::BufferView>>("bufferView",
+                                                                                                            gltf2::RefReader<gltf2::Document>::Read<gltf2::BufferView, &gltf2::Document::mBufferViews>,
+                                                                                                            &gltf2::Accessor::mBufferView))
+                                         .Register(*new json::Property<gltf2::Accessor, uint32_t>("byteOffset",
+                                                                                             json::Read::Number<uint32_t>,
+                                                                                             &gltf2::Accessor::mByteOffset))
+                                         .Register(*new json::Property<gltf2::Accessor, gltf2::Component::Type>("componentType",
+                                                                                                        json::Read::Enum<gltf2::Component::Type>,
+                                                                                                        &gltf2::Accessor::mComponentType))
+                                         .Register(*new json::Property<gltf2::Accessor, std::string_view>("name", json::Read::StringView, &gltf2::Accessor::mName))
+                                         .Register(*json::MakeProperty("count", json::Read::Number<uint32_t>, &gltf2::Accessor::mCount))
+                                         .Register(*json::MakeProperty("normalized", json::Read::Boolean, &gltf2::Accessor::mNormalized))
+                                         .Register(*json::MakeProperty("type", gltf2::ReadStringEnum<gltf2::AccessorType>, &gltf2::Accessor::mType))
+                                         .Register(*json::MakeProperty("min", json::Read::Array<float, json::Read::Number>, &gltf2::Accessor::mMin))
+                                         .Register(*json::MakeProperty("max", json::Read::Array<float, json::Read::Number>, &gltf2::Accessor::mMax))
+                                         .Register(*new json::Property<gltf2::Accessor, gltf2::Accessor::Sparse>("sparse", json::ObjectReader<gltf2::Accessor::Sparse>::Read, &gltf2::Accessor::SetSparse)));
+
+const auto IMAGE_READER = std::move(json::Reader<gltf2::Image>()
+                                      .Register(*new json::Property<gltf2::Image, std::string_view>("name", json::Read::StringView, &gltf2::Material::mName))
+                                      .Register(*json::MakeProperty("uri", json::Read::StringView, &gltf2::Image::mUri))
+                                      .Register(*json::MakeProperty("mimeType", json::Read::StringView, &gltf2::Image::mMimeType))
+                                      .Register(*json::MakeProperty("bufferView", gltf2::RefReader<gltf2::Document>::Read<gltf2::BufferView, &gltf2::Document::mBufferViews>, &gltf2::Image::mBufferView)));
+
+const auto SAMPLER_READER = std::move(json::Reader<gltf2::Sampler>()
+                                        .Register(*json::MakeProperty("minFilter", json::Read::Enum<gltf2::Filter::Type>, &gltf2::Sampler::mMinFilter))
+                                        .Register(*json::MakeProperty("magFilter", json::Read::Enum<gltf2::Filter::Type>, &gltf2::Sampler::mMagFilter))
+                                        .Register(*json::MakeProperty("wrapS", json::Read::Enum<gltf2::Wrap::Type>, &gltf2::Sampler::mWrapS))
+                                        .Register(*json::MakeProperty("wrapT", json::Read::Enum<gltf2::Wrap::Type>, &gltf2::Sampler::mWrapT)));
+
+const auto TEXURE_READER = std::move(json::Reader<gltf2::Texture>()
+                                       .Register(*json::MakeProperty("source", gltf2::RefReader<gltf2::Document>::Read<gltf2::Image, &gltf2::Document::mImages>, &gltf2::Texture::mSource))
+                                       .Register(*json::MakeProperty("sampler", gltf2::RefReader<gltf2::Document>::Read<gltf2::Sampler, &gltf2::Document::mSamplers>, &gltf2::Texture::mSampler)));
+
+const auto TEXURE_INFO_READER = std::move(json::Reader<gltf2::TextureInfo>()
+                                            .Register(*json::MakeProperty("index", gltf2::RefReader<gltf2::Document>::Read<gltf2::Texture, &gltf2::Document::mTextures>, &gltf2::TextureInfo::mTexture))
+                                            .Register(*json::MakeProperty("texCoord", json::Read::Number<uint32_t>, &gltf2::TextureInfo::mTexCoord))
+                                            .Register(*json::MakeProperty("scale", json::Read::Number<float>, &gltf2::TextureInfo::mScale))
+                                            .Register(*json::MakeProperty("strength", json::Read::Number<float>, &gltf2::TextureInfo::mStrength)));
+
+const auto MATERIAL_PBR_READER = std::move(json::Reader<gltf2::Material::Pbr>()
+                                             .Register(*json::MakeProperty("baseColorFactor", gltf2::ReadDaliVector<Vector4>, &gltf2::Material::Pbr::mBaseColorFactor))
+                                             .Register(*json::MakeProperty("baseColorTexture", json::ObjectReader<gltf2::TextureInfo>::Read, &gltf2::Material::Pbr::mBaseColorTexture))
+                                             .Register(*json::MakeProperty("metallicFactor", json::Read::Number<float>, &gltf2::Material::Pbr::mMetallicFactor))
+                                             .Register(*json::MakeProperty("roughnessFactor", json::Read::Number<float>, &gltf2::Material::Pbr::mRoughnessFactor))
+                                             .Register(*json::MakeProperty("metallicRoughnessTexture", json::ObjectReader<gltf2::TextureInfo>::Read, &gltf2::Material::Pbr::mMetallicRoughnessTexture)));
+
+const auto MATERIAL_SPECULAR_READER = std::move(json::Reader<gltf2::MaterialSpecular>()
+                                                  .Register(*json::MakeProperty("specularFactor", json::Read::Number<float>, &gltf2::MaterialSpecular::mSpecularFactor))
+                                                  .Register(*json::MakeProperty("specularTexture", json::ObjectReader<gltf2::TextureInfo>::Read, &gltf2::MaterialSpecular::mSpecularTexture))
+                                                  .Register(*json::MakeProperty("specularColorFactor", gltf2::ReadDaliVector<Vector3>, &gltf2::MaterialSpecular::mSpecularColorFactor))
+                                                  .Register(*json::MakeProperty("specularColorTexture", json::ObjectReader<gltf2::TextureInfo>::Read, &gltf2::MaterialSpecular::mSpecularColorTexture)));
+
+const auto MATERIAL_IOR_READER = std::move(json::Reader<gltf2::MaterialIor>()
+                                             .Register(*json::MakeProperty("ior", json::Read::Number<float>, &gltf2::MaterialIor::mIor)));
+
+const auto MATERIAL_EXTENSION_READER = std::move(json::Reader<gltf2::MaterialExtensions>()
+                                                   .Register(*json::MakeProperty("KHR_materials_ior", json::ObjectReader<gltf2::MaterialIor>::Read, &gltf2::MaterialExtensions::mMaterialIor))
+                                                   .Register(*json::MakeProperty("KHR_materials_specular", json::ObjectReader<gltf2::MaterialSpecular>::Read, &gltf2::MaterialExtensions::mMaterialSpecular)));
+
+const auto MATERIAL_READER = std::move(json::Reader<gltf2::Material>()
+                                         .Register(*new json::Property<gltf2::Material, std::string_view>("name", json::Read::StringView, &gltf2::Material::mName))
+                                         .Register(*json::MakeProperty("pbrMetallicRoughness", json::ObjectReader<gltf2::Material::Pbr>::Read, &gltf2::Material::mPbrMetallicRoughness))
+                                         .Register(*json::MakeProperty("normalTexture", json::ObjectReader<gltf2::TextureInfo>::Read, &gltf2::Material::mNormalTexture))
+                                         .Register(*json::MakeProperty("occlusionTexture", json::ObjectReader<gltf2::TextureInfo>::Read, &gltf2::Material::mOcclusionTexture))
+                                         .Register(*json::MakeProperty("emissiveTexture", json::ObjectReader<gltf2::TextureInfo>::Read, &gltf2::Material::mEmissiveTexture))
+                                         .Register(*json::MakeProperty("emissiveFactor", gltf2::ReadDaliVector<Vector3>, &gltf2::Material::mEmissiveFactor))
+                                         .Register(*json::MakeProperty("alphaMode", gltf2::ReadStringEnum<gltf2::AlphaMode>, &gltf2::Material::mAlphaMode))
+                                         .Register(*json::MakeProperty("alphaCutoff", json::Read::Number<float>, &gltf2::Material::mAlphaCutoff))
+                                         .Register(*json::MakeProperty("doubleSided", json::Read::Boolean, &gltf2::Material::mDoubleSided))
+                                         .Register(*json::MakeProperty("extensions", json::ObjectReader<gltf2::MaterialExtensions>::Read, &gltf2::Material::mMaterialExtensions)));
+
+std::map<gltf2::Attribute::Type, gltf2::Ref<gltf2::Accessor>> ReadMeshPrimitiveAttributes(const json_value_s& j)
+{
+  auto&                                                jo = json::Cast<json_object_s>(j);
+  std::map<gltf2::Attribute::Type, gltf2::Ref<gltf2::Accessor>> result;
+
+  auto i = jo.start;
+  while(i)
+  {
+    auto jstr                                                        = *i->name;
+    result[gltf2::Attribute::FromString(jstr.string, jstr.string_size)] = gltf2::RefReader<gltf2::Document>::Read<gltf2::Accessor, &gltf2::Document::mAccessors>(*i->value);
+    i                                                                = i->next;
+  }
+  return result;
+}
+
+std::vector<std::map<gltf2::Attribute::Type, gltf2::Ref<gltf2::Accessor>>> ReadMeshPrimitiveTargets(const json_value_s& j)
+{
+  auto&                                                             jo = json::Cast<json_array_s>(j);
+  std::vector<std::map<gltf2::Attribute::Type, gltf2::Ref<gltf2::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(json::Reader<gltf2::Mesh::Primitive>()
+                                               .Register(*json::MakeProperty("attributes", ReadMeshPrimitiveAttributes, &gltf2::Mesh::Primitive::mAttributes))
+                                               .Register(*json::MakeProperty("indices", gltf2::RefReader<gltf2::Document>::Read<gltf2::Accessor, &gltf2::Document::mAccessors>, &gltf2::Mesh::Primitive::mIndices))
+                                               .Register(*json::MakeProperty("material", gltf2::RefReader<gltf2::Document>::Read<gltf2::Material, &gltf2::Document::mMaterials>, &gltf2::Mesh::Primitive::mMaterial))
+                                               .Register(*json::MakeProperty("mode", json::Read::Enum<gltf2::Mesh::Primitive::Mode>, &gltf2::Mesh::Primitive::mMode))
+                                               .Register(*json::MakeProperty("targets", ReadMeshPrimitiveTargets, &gltf2::Mesh::Primitive::mTargets)));
+
+const auto MESH_READER = std::move(json::Reader<gltf2::Mesh>()
+                                     .Register(*new json::Property<gltf2::Mesh, std::string_view>("name", json::Read::StringView, &gltf2::Mesh::mName))
+                                     .Register(*json::MakeProperty("primitives",
+                                                                 json::Read::Array<gltf2::Mesh::Primitive, json::ObjectReader<gltf2::Mesh::Primitive>::Read>,
+                                                                 &gltf2::Mesh::mPrimitives))
+                                     .Register(*json::MakeProperty("weights", json::Read::Array<float, json::Read::Number>, &gltf2::Mesh::mWeights)));
+
+const auto SKIN_READER = std::move(json::Reader<gltf2::Skin>()
+                                     .Register(*new json::Property<gltf2::Skin, std::string_view>("name", json::Read::StringView, &gltf2::Skin::mName))
+                                     .Register(*json::MakeProperty("inverseBindMatrices",
+                                                                 gltf2::RefReader<gltf2::Document>::Read<gltf2::Accessor, &gltf2::Document::mAccessors>,
+                                                                 &gltf2::Skin::mInverseBindMatrices))
+                                     .Register(*json::MakeProperty("skeleton",
+                                                                 gltf2::RefReader<gltf2::Document>::Read<gltf2::Node, &gltf2::Document::mNodes>,
+                                                                 &gltf2::Skin::mSkeleton))
+                                     .Register(*json::MakeProperty("joints",
+                                                                 json::Read::Array<gltf2::Ref<gltf2::Node>, gltf2::RefReader<gltf2::Document>::Read<gltf2::Node, &gltf2::Document::mNodes>>,
+                                                                 &gltf2::Skin::mJoints)));
+
+const auto CAMERA_PERSPECTIVE_READER = std::move(json::Reader<gltf2::Camera::Perspective>()
+                                                   .Register(*json::MakeProperty("aspectRatio", json::Read::Number<float>, &gltf2::Camera::Perspective::mAspectRatio))
+                                                   .Register(*json::MakeProperty("yfov", json::Read::Number<float>, &gltf2::Camera::Perspective::mYFov))
+                                                   .Register(*json::MakeProperty("zfar", json::Read::Number<float>, &gltf2::Camera::Perspective::mZFar))
+                                                   .Register(*json::MakeProperty("znear", json::Read::Number<float>, &gltf2::Camera::Perspective::mZNear))); // TODO: infinite perspective projection, where znear is omitted
+
+const auto CAMERA_ORTHOGRAPHIC_READER = std::move(json::Reader<gltf2::Camera::Orthographic>()
+                                                    .Register(*json::MakeProperty("xmag", json::Read::Number<float>, &gltf2::Camera::Orthographic::mXMag))
+                                                    .Register(*json::MakeProperty("ymag", json::Read::Number<float>, &gltf2::Camera::Orthographic::mYMag))
+                                                    .Register(*json::MakeProperty("zfar", json::Read::Number<float>, &gltf2::Camera::Orthographic::mZFar))
+                                                    .Register(*json::MakeProperty("znear", json::Read::Number<float>, &gltf2::Camera::Orthographic::mZNear)));
+
+const auto CAMERA_READER = std::move(json::Reader<gltf2::Camera>()
+                                       .Register(*new json::Property<gltf2::Camera, std::string_view>("name", json::Read::StringView, &gltf2::Camera::mName))
+                                       .Register(*json::MakeProperty("type", json::Read::StringView, &gltf2::Camera::mType))
+                                       .Register(*json::MakeProperty("perspective", json::ObjectReader<gltf2::Camera::Perspective>::Read, &gltf2::Camera::mPerspective))
+                                       .Register(*json::MakeProperty("orthographic", json::ObjectReader<gltf2::Camera::Orthographic>::Read, &gltf2::Camera::mOrthographic)));
+
+const auto NODE_READER = std::move(json::Reader<gltf2::Node>()
+                                     .Register(*new json::Property<gltf2::Node, std::string_view>("name", json::Read::StringView, &gltf2::Node::mName))
+                                     .Register(*json::MakeProperty("translation", gltf2::ReadDaliVector<Vector3>, &gltf2::Node::mTranslation))
+                                     .Register(*json::MakeProperty("rotation", gltf2::ReadQuaternion, &gltf2::Node::mRotation))
+                                     .Register(*json::MakeProperty("scale", gltf2::ReadDaliVector<Vector3>, &gltf2::Node::mScale))
+                                     .Register(*new json::Property<gltf2::Node, Matrix>("matrix", gltf2::ReadDaliVector<Matrix>, &gltf2::Node::SetMatrix))
+                                     .Register(*json::MakeProperty("camera", gltf2::RefReader<gltf2::Document>::Read<gltf2::Camera, &gltf2::Document::mCameras>, &gltf2::Node::mCamera))
+                                     .Register(*json::MakeProperty("children", json::Read::Array<gltf2::Ref<gltf2::Node>, gltf2::RefReader<gltf2::Document>::Read<gltf2::Node, &gltf2::Document::mNodes>>, &gltf2::Node::mChildren))
+                                     .Register(*json::MakeProperty("mesh", gltf2::RefReader<gltf2::Document>::Read<gltf2::Mesh, &gltf2::Document::mMeshes>, &gltf2::Node::mMesh))
+                                     .Register(*json::MakeProperty("skin", gltf2::RefReader<gltf2::Document>::Read<gltf2::Skin, &gltf2::Document::mSkins>, &gltf2::Node::mSkin)));
+
+const auto ANIMATION_SAMPLER_READER = std::move(json::Reader<gltf2::Animation::Sampler>()
+                                                  .Register(*json::MakeProperty("input", gltf2::RefReader<gltf2::Document>::Read<gltf2::Accessor, &gltf2::Document::mAccessors>, &gltf2::Animation::Sampler::mInput))
+                                                  .Register(*json::MakeProperty("output", gltf2::RefReader<gltf2::Document>::Read<gltf2::Accessor, &gltf2::Document::mAccessors>, &gltf2::Animation::Sampler::mOutput))
+                                                  .Register(*json::MakeProperty("interpolation", gltf2::ReadStringEnum<gltf2::Animation::Sampler::Interpolation>, &gltf2::Animation::Sampler::mInterpolation)));
+
+const auto ANIMATION_TARGET_READER = std::move(json::Reader<gltf2::Animation::Channel::Target>()
+                                                 .Register(*json::MakeProperty("node", gltf2::RefReader<gltf2::Document>::Read<gltf2::Node, &gltf2::Document::mNodes>, &gltf2::Animation::Channel::Target::mNode))
+                                                 .Register(*json::MakeProperty("path", gltf2::ReadStringEnum<gltf2::Animation::Channel::Target>, &gltf2::Animation::Channel::Target::mPath)));
+
+const auto ANIMATION_CHANNEL_READER = std::move(json::Reader<gltf2::Animation::Channel>()
+                                                  .Register(*json::MakeProperty("target", json::ObjectReader<gltf2::Animation::Channel::Target>::Read, &gltf2::Animation::Channel::mTarget))
+                                                  .Register(*json::MakeProperty("sampler", gltf2::RefReader<gltf2::Animation>::Read<gltf2::Animation::Sampler, &gltf2::Animation::mSamplers>, &gltf2::Animation::Channel::mSampler)));
+
+const auto ANIMATION_READER = std::move(json::Reader<gltf2::Animation>()
+                                          .Register(*new json::Property<gltf2::Animation, std::string_view>("name", json::Read::StringView, &gltf2::Animation::mName))
+                                          .Register(*json::MakeProperty("samplers",
+                                                                      json::Read::Array<gltf2::Animation::Sampler, json::ObjectReader<gltf2::Animation::Sampler>::Read>,
+                                                                      &gltf2::Animation::mSamplers))
+                                          .Register(*json::MakeProperty("channels",
+                                                                      json::Read::Array<gltf2::Animation::Channel, json::ObjectReader<gltf2::Animation::Channel>::Read>,
+                                                                      &gltf2::Animation::mChannels)));
+
+const auto SCENE_READER = std::move(json::Reader<gltf2::Scene>()
+                                      .Register(*new json::Property<gltf2::Scene, std::string_view>("name", json::Read::StringView, &gltf2::Scene::mName))
+                                      .Register(*json::MakeProperty("nodes",
+                                                                  json::Read::Array<gltf2::Ref<gltf2::Node>, gltf2::RefReader<gltf2::Document>::Read<gltf2::Node, &gltf2::Document::mNodes>>,
+                                                                  &gltf2::Scene::mNodes)));
+
+const auto DOCUMENT_READER = std::move(json::Reader<gltf2::Document>()
+                                         .Register(*json::MakeProperty("buffers",
+                                                                     json::Read::Array<gltf2::Buffer, json::ObjectReader<gltf2::Buffer>::Read>,
+                                                                     &gltf2::Document::mBuffers))
+                                         .Register(*json::MakeProperty("bufferViews",
+                                                                     json::Read::Array<gltf2::BufferView, json::ObjectReader<gltf2::BufferView>::Read>,
+                                                                     &gltf2::Document::mBufferViews))
+                                         .Register(*json::MakeProperty("accessors",
+                                                                     json::Read::Array<gltf2::Accessor, json::ObjectReader<gltf2::Accessor>::Read>,
+                                                                     &gltf2::Document::mAccessors))
+                                         .Register(*json::MakeProperty("images",
+                                                                     json::Read::Array<gltf2::Image, json::ObjectReader<gltf2::Image>::Read>,
+                                                                     &gltf2::Document::mImages))
+                                         .Register(*json::MakeProperty("samplers",
+                                                                     json::Read::Array<gltf2::Sampler, json::ObjectReader<gltf2::Sampler>::Read>,
+                                                                     &gltf2::Document::mSamplers))
+                                         .Register(*json::MakeProperty("textures",
+                                                                     json::Read::Array<gltf2::Texture, json::ObjectReader<gltf2::Texture>::Read>,
+                                                                     &gltf2::Document::mTextures))
+                                         .Register(*json::MakeProperty("materials",
+                                                                     json::Read::Array<gltf2::Material, json::ObjectReader<gltf2::Material>::Read>,
+                                                                     &gltf2::Document::mMaterials))
+                                         .Register(*json::MakeProperty("meshes",
+                                                                     json::Read::Array<gltf2::Mesh, json::ObjectReader<gltf2::Mesh>::Read>,
+                                                                     &gltf2::Document::mMeshes))
+                                         .Register(*json::MakeProperty("skins",
+                                                                     json::Read::Array<gltf2::Skin, json::ObjectReader<gltf2::Skin>::Read>,
+                                                                     &gltf2::Document::mSkins))
+                                         .Register(*json::MakeProperty("cameras",
+                                                                     json::Read::Array<gltf2::Camera, json::ObjectReader<gltf2::Camera>::Read>,
+                                                                     &gltf2::Document::mCameras))
+                                         .Register(*json::MakeProperty("nodes",
+                                                                     json::Read::Array<gltf2::Node, json::ObjectReader<gltf2::Node>::Read>,
+                                                                     &gltf2::Document::mNodes))
+                                         .Register(*json::MakeProperty("animations",
+                                                                     ReadAnimationArray,
+                                                                     &gltf2::Document::mAnimations))
+                                         .Register(*json::MakeProperty("scenes",
+                                                                     json::Read::Array<gltf2::Scene, json::ObjectReader<gltf2::Scene>::Read>,
+                                                                     &gltf2::Document::mScenes))
+                                         .Register(*json::MakeProperty("scene", gltf2::RefReader<gltf2::Document>::Read<gltf2::Scene, &gltf2::Document::mScenes>, &gltf2::Document::mScene)));
+
+void ConvertBuffer(const gltf2::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 gltf2::Document& doc, ConversionContext& context)
+{
+  auto& outBuffers = context.mOutput.mResources.mBuffers;
+  outBuffers.reserve(doc.mBuffers.size());
+
+  for(auto& buffer : doc.mBuffers)
+  {
+    if(buffer.mUri.empty())
+    {
+      continue;
+    }
+    ConvertBuffer(buffer, outBuffers, context.mPath);
+  }
+}
+
+SamplerFlags::Type ConvertWrapMode(gltf2::Wrap::Type wrapMode)
+{
+  switch(wrapMode)
+  {
+    case gltf2::Wrap::REPEAT:
+      return SamplerFlags::WRAP_REPEAT;
+    case gltf2::Wrap::CLAMP_TO_EDGE:
+      return SamplerFlags::WRAP_CLAMP;
+    case gltf2::Wrap::MIRRORED_REPEAT:
+      return SamplerFlags::WRAP_MIRROR;
+    default:
+      throw std::runtime_error("Invalid wrap type.");
+  }
+}
+
+SamplerFlags::Type ConvertSampler(const gltf2::Ref<gltf2::Sampler>& sampler)
+{
+  if(sampler)
+  {
+    return ((sampler->mMinFilter < gltf2::Filter::NEAREST_MIPMAP_NEAREST) ? (sampler->mMinFilter - gltf2::Filter::NEAREST) : ((sampler->mMinFilter - gltf2::Filter::NEAREST_MIPMAP_NEAREST) + 2)) |
+           ((sampler->mMagFilter - gltf2::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 gltf2::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 gltf2::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 gltf2::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 == gltf2::AlphaMode::BLEND)
+  {
+    matDef.mIsOpaque = false;
+    matDef.mFlags |= MaterialDefinition::TRANSPARENCY;
+  }
+  else if(material.mAlphaMode == gltf2::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 gltf2::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 gltf2::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 gltf2::Accessor::Sparse&               sparse  = *acc.mSparse;
+    const gltf2::ComponentTypedBufferViewClient& indices = sparse.mIndices;
+    const gltf2::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 gltf2::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(gltf2::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 == gltf2::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 == gltf2::Attribute::JOINTS_0)
+          {
+            meshDefinition.mFlags |= (iFind->second->mComponentType == gltf2::Component::UNSIGNED_SHORT) * MeshDefinition::U16_JOINT_IDS;
+            meshDefinition.mFlags |= (iFind->second->mComponentType == gltf2::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 == gltf2::Component::FLOAT);
+          }
+        }
+        else if(needNormalsTangents)
+        {
+          switch(am.mType)
+          {
+            case gltf2::Attribute::NORMAL:
+              meshDefinition.RequestNormals();
+              break;
+
+            case gltf2::Attribute::TANGENT:
+              meshDefinition.RequestTangents();
+              break;
+
+            default:
+              break;
+          }
+        }
+      }
+
+      if(primitive.mIndices)
+      {
+        meshDefinition.mIndices = ConvertMeshPrimitiveAccessor(*primitive.mIndices);
+        meshDefinition.mFlags |= (primitive.mIndices->mComponentType == gltf2::Component::UNSIGNED_INT) * MeshDefinition::U32_INDICES;
+        meshDefinition.mFlags |= (primitive.mIndices->mComponentType == gltf2::Component::UNSIGNED_BYTE) * MeshDefinition::U8_INDICES;
+        DALI_ASSERT_DEBUG(MaskMatch(meshDefinition.mFlags, MeshDefinition::U32_INDICES) || MaskMatch(meshDefinition.mFlags, MeshDefinition::U8_INDICES) || primitive.mIndices->mComponentType == gltf2::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(gltf2::Attribute::POSITION);
+          if(it != endIt)
+          {
+            blendShape.deltas = ConvertMeshPrimitiveAccessor(*it->second);
+          }
+          it = target.find(gltf2::Attribute::NORMAL);
+          if(it != endIt)
+          {
+            blendShape.normals = ConvertMeshPrimitiveAccessor(*it->second);
+          }
+          it = target.find(gltf2::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 gltf2::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(gltf2::Material{}, context.mOutput.mSceneMetadata.mImageMetadata, outMaterials, context);
+    }
+
+    materialIdx = context.mDefaultMaterial;
+  }
+
+  modelRenderable->mMaterialIdx = materialIdx;
+
+  return modelRenderable;
+}
+
+void ConvertCamera(const gltf2::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(gltf2::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 gltf2::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 gltf2::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 gltf2::Animation::Channel& channel, KeyFrames& keyFrames, gltf2::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 gltf2::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.data());
+  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 gltf2::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 == gltf2::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 gltf2::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 gltf2::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 gltf2::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 gltf2::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 gltf2::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 gltf2::Accessor& accessor, ConversionContext& context)
+    : mStream(context.mOutput.mResources.mBuffers[accessor.mBufferView->mBuffer.GetIndex()].GetBufferStream()),
+      mElementSizeBytes(accessor.GetElementSizeBytes())
+    {
+      DALI_ASSERT_DEBUG(accessor.mType == gltf2::AccessorType::MAT4 && accessor.mComponentType == gltf2::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, Dali::Scene3D::Loader::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()
+{
+  json::SetObjectReader(BUFFER_READER);
+  json::SetObjectReader(BUFFER_VIEW_READER);
+  json::SetObjectReader(BUFFER_VIEW_CLIENT_READER);
+  json::SetObjectReader(COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER);
+  json::SetObjectReader(ACCESSOR_SPARSE_READER);
+  json::SetObjectReader(ACCESSOR_READER);
+  json::SetObjectReader(IMAGE_READER);
+  json::SetObjectReader(SAMPLER_READER);
+  json::SetObjectReader(TEXURE_READER);
+  json::SetObjectReader(TEXURE_INFO_READER);
+  json::SetObjectReader(MATERIAL_PBR_READER);
+  json::SetObjectReader(MATERIAL_SPECULAR_READER);
+  json::SetObjectReader(MATERIAL_IOR_READER);
+  json::SetObjectReader(MATERIAL_EXTENSION_READER);
+  json::SetObjectReader(MATERIAL_READER);
+  json::SetObjectReader(MESH_PRIMITIVE_READER);
+  json::SetObjectReader(MESH_READER);
+  json::SetObjectReader(SKIN_READER);
+  json::SetObjectReader(CAMERA_PERSPECTIVE_READER);
+  json::SetObjectReader(CAMERA_ORTHOGRAPHIC_READER);
+  json::SetObjectReader(CAMERA_READER);
+  json::SetObjectReader(NODE_READER);
+  json::SetObjectReader(ANIMATION_SAMPLER_READER);
+  json::SetObjectReader(ANIMATION_TARGET_READER);
+  json::SetObjectReader(ANIMATION_CHANNEL_READER);
+  json::SetObjectReader(ANIMATION_READER);
+  json::SetObjectReader(SCENE_READER);
+}
+
+void SetDefaultEnvironmentMap(const gltf2::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()});
+}
+
+void InitializeGltfLoader()
+{
+  static Dali::Mutex gInitializeMutex;
+  // 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;
+    }
+  }
+}
+
+const std::string_view GetRendererModelIdentification()
+{
+  return MRENDERER_MODEL_IDENTIFICATION;
+}
+
+void ReadDocument(const json_object_s& jsonObject, gltf2::Document& document)
+{
+  DOCUMENT_READER.Read(jsonObject, document);
+}
+
+void ReadDocumentFromParsedData(const json_object_s& jsonObject, gltf2::Document& document)
+{
+  static Dali::Mutex gReadMutex;
+  Mutex::ScopedLock  lock(gReadMutex);
+  gt::SetRefReaderObject(document);
+  Gltf2Util::ReadDocument(jsonObject, document);
+}
+
+bool GenerateDocument(json::unique_ptr& root, gt::Document& document, bool& isMRendererModel)
+{
+  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)
+  {
+    document.mAsset.mVersion = js::Read::StringView(*jsAssetVersion);
+  }
+
+  auto jsAssetGenerator = js::FindObjectChild("generator", js::Cast<json_object_s>(*jsAsset));
+  if(jsAssetGenerator)
+  {
+    document.mAsset.mGenerator = js::Read::StringView(*jsAssetGenerator);
+    isMRendererModel           = (document.mAsset.mGenerator.find(Gltf2Util::GetRendererModelIdentification().data()) != std::string_view::npos);
+  }
+
+  Gltf2Util::InitializeGltfLoader();
+  Gltf2Util::ReadDocumentFromParsedData(rootObj, document);
+
+  return true;
+}
+
+void ConvertGltfToContext(gt::Document& document, Gltf2Util::ConversionContext& context, bool isMRendererModel)
+{
+  Dali::Scene3D::Loader::ShaderDefinitionFactory shaderFactory;
+  shaderFactory.SetResources(context.mOutput.mResources);
+
+  Gltf2Util::ConvertBuffers(document, context);
+  Gltf2Util::ConvertMaterials(document, context);
+  Gltf2Util::ConvertMeshes(document, context);
+  Gltf2Util::ConvertNodes(document, context, isMRendererModel);
+  Gltf2Util::ConvertAnimations(document, context);
+  Gltf2Util::ProcessSkins(document, context);
+  Gltf2Util::ProduceShaders(shaderFactory, context.mOutput.mScene);
+  context.mOutput.mScene.EnsureUniqueSkinningShaderInstances(context.mOutput.mResources);
+
+  // Set Default Environment map
+  Gltf2Util::SetDefaultEnvironmentMap(document, context);
+}
+
+} // namespace Gltf2Util
+
+} // namespace Internal
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
diff --git a/dali-scene3d/internal/loader/gltf2-util.h b/dali-scene3d/internal/loader/gltf2-util.h
new file mode 100644 (file)
index 0000000..d84ce05
--- /dev/null
@@ -0,0 +1,135 @@
+#ifndef DALI_SCENE3D_LOADER_GLTF2_UTIL_H
+#define DALI_SCENE3D_LOADER_GLTF2_UTIL_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/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>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/threading/mutex.h>
+#include <dali/integration-api/debug.h>
+
+namespace gt = gltf2;
+namespace js = json;
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Loader
+{
+namespace Internal
+{
+namespace Gltf2Util
+{
+
+struct NodeMapping
+{
+  Index gltfIdx;
+  Index runtimeIdx;
+
+  bool operator<(Index gltfIdx) const
+  {
+    return this->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 ConvertBuffers(const gt::Document& doc, ConversionContext& context);
+
+void ConvertMaterials(const gt::Document& doc, ConversionContext& context);
+
+void ConvertMeshes(const gt::Document& doc, ConversionContext& context);
+
+void ConvertCamera(const gt::Camera& camera, CameraParameters& camParams);
+
+void ConvertNodes(const gt::Document& doc, ConversionContext& context, bool isMRendererModel);
+
+void ConvertAnimations(const gt::Document& doc, ConversionContext& context);
+
+void ProcessSkins(const gt::Document& doc, ConversionContext& context);
+
+void ProduceShaders(ShaderDefinitionFactory& shaderFactory, SceneDefinition& scene);
+
+void SetDefaultEnvironmentMap(const gt::Document& doc, ConversionContext& context);
+
+const std::string_view GetRendererModelIdentification();
+
+void ReadDocument(const json_object_s& jsonObject, gt::Document& document);
+
+void InitializeGltfLoader();
+
+void ReadDocumentFromParsedData(const json_object_s& jsonObject, gltf2::Document& document);
+
+bool GenerateDocument(json::unique_ptr& root, gt::Document& document, bool& isMRendererModel);
+
+void ConvertGltfToContext(gt::Document& document, Gltf2Util::ConversionContext& context, bool isMRendererModel);
+
+} // namespace Gltf2Util
+
+} // namespace Internal
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
+
+#endif // DALI_SCENE3D_LOADER_GLTF2_UTIL_H
index 1704927..2b85631 100644 (file)
@@ -42,6 +42,14 @@ struct BufferDefinition::Impl
   std::shared_ptr<Dali::FileStream> stream;
 };
 
+BufferDefinition::BufferDefinition(std::vector<uint8_t>& buffer)
+: mImpl{new BufferDefinition::Impl}
+{
+  mImpl.get()->buffer = std::move(buffer);
+  mImpl.get()->stream = std::make_shared<Dali::FileStream>(reinterpret_cast<uint8_t*>(mImpl.get()->buffer.data()), mImpl.get()->buffer.size(), FileStream::READ | FileStream::BINARY);
+  mIsEmbedded         = true;
+}
+
 BufferDefinition::BufferDefinition()
 : mImpl{new BufferDefinition::Impl}
 {
index 8f6952a..808d5a0 100644 (file)
@@ -24,6 +24,7 @@
 #include <fstream>
 #include <memory>
 #include <vector>
+#include <dali/public-api/common/dali-vector.h>
 
 namespace Dali
 {
@@ -40,6 +41,8 @@ struct DALI_SCENE3D_API BufferDefinition
   using Vector = std::vector<BufferDefinition>;
 
   BufferDefinition();
+  BufferDefinition(std::vector<uint8_t>& buffer);
+
   ~BufferDefinition();
 
   BufferDefinition(const BufferDefinition& other)            = default;
index 5ca930f..219f0a9 100644 (file)
@@ -26,6 +26,7 @@
 // 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/glb-loader-impl.h>
 #include <dali-scene3d/internal/loader/model-loader-impl.h>
 
 namespace Dali
@@ -38,6 +39,7 @@ namespace
 {
 static constexpr std::string_view OBJ_EXTENSION      = ".obj";
 static constexpr std::string_view GLTF_EXTENSION     = ".gltf";
+static constexpr std::string_view GLB_EXTENSION      = ".glb";
 static constexpr std::string_view DLI_EXTENSION      = ".dli";
 static constexpr std::string_view METADATA_EXTENSION = "metadata";
 } // namespace
@@ -118,6 +120,10 @@ void ModelLoader::CreateModelLoader()
   {
     mImpl = std::make_shared<Dali::Scene3D::Loader::Internal::Gltf2LoaderImpl>();
   }
+  else if(extension == GLB_EXTENSION)
+  {
+    mImpl = std::make_shared<Dali::Scene3D::Loader::Internal::GlbLoaderImpl>();
+  }
   else
   {
     DALI_LOG_ERROR("Not supported model format : %s\n", extension.c_str());