[dali_2.2.16] Merge branch 'devel/master' 71/289271/1
authorAdam Bialogonski <adam.b@samsung.com>
Fri, 3 Mar 2023 10:26:48 +0000 (10:26 +0000)
committerAdam Bialogonski <adam.b@samsung.com>
Fri, 3 Mar 2023 10:26:48 +0000 (10:26 +0000)
Change-Id: I9d57f44aaedc074540361181a6acaaa7c40722be

32 files changed:
automated-tests/resources/AnimatedCube.gltf
automated-tests/resources/EnvironmentTest.gltf
automated-tests/resources/EnvironmentTest_images/roughness_metallic_0.jpg [new file with mode: 0644]
automated-tests/resources/EnvironmentTest_images/roughness_metallic_1.jpg [new file with mode: 0644]
automated-tests/src/dali-scene3d-internal/CMakeLists.txt
automated-tests/src/dali-scene3d-internal/utc-Dali-ModelCacheManager.cpp [new file with mode: 0644]
automated-tests/src/dali-scene3d/utc-Dali-CameraParameters.cpp
automated-tests/src/dali-scene3d/utc-Dali-Gltf2Loader.cpp
automated-tests/src/dali-scene3d/utc-Dali-Model.cpp
automated-tests/src/dali-scene3d/utc-Dali-SceneView.cpp
dali-scene3d/internal/common/model-cache-manager.cpp [new file with mode: 0644]
dali-scene3d/internal/common/model-cache-manager.h [new file with mode: 0644]
dali-scene3d/internal/common/model-load-task.cpp
dali-scene3d/internal/common/model-load-task.h
dali-scene3d/internal/controls/model/model-impl.cpp
dali-scene3d/internal/controls/model/model-impl.h
dali-scene3d/internal/file.list
dali-scene3d/internal/loader/gltf2-asset.h
dali-scene3d/public-api/controls/model/model.cpp
dali-scene3d/public-api/controls/model/model.h
dali-scene3d/public-api/loader/camera-parameters.cpp
dali-scene3d/public-api/loader/camera-parameters.h
dali-scene3d/public-api/loader/dli-loader.cpp
dali-scene3d/public-api/loader/facial-animation-loader.cpp
dali-scene3d/public-api/loader/gltf2-loader.cpp
dali-scene3d/public-api/loader/load-scene-metadata.cpp
dali-scene3d/public-api/loader/mesh-definition.cpp
dali-scene3d/public-api/loader/resource-bundle.cpp
dali-scene3d/public-api/loader/resource-bundle.h
dali-toolkit/internal/text/multi-language-support-impl.cpp
dali-toolkit/public-api/dali-toolkit-version.cpp
packaging/dali-toolkit.spec

index 7787c04..807c07e 100644 (file)
         "camera" : 1,
         "translation" : [ 0.5, 0.5, 3.0 ],
         "children": [
-          4
+          4, 5, 6, 7
         ]
       },
       {
             0.0,
             1.0
         ]
+      },
+      {
+        "camera" : 3,
+        "translation" : [ 0.0, 0.0, 0.0 ]
+      },
+      {
+        "camera" : 4,
+        "translation" : [ 0.0, 0.0, 0.0 ]
+      },
+      {
+        "camera" : 5,
+        "translation" : [ 0.0, 0.0, 0.0 ]
       }
    ],
    "scene" : 0,
         "zfar": 100.0,
         "znear": 0.01
       }
+    },
+    {
+      "type": "perspective",
+      "perspective": {
+        "aspectRatio": 1.0,
+        "yfov": 0.7,
+        "znear": 0.01
+      }
+    },
+    {
+      "type": "perspective",
+      "perspective": {
+        "aspectRatio": 1.0,
+        "zfar": 100.0,
+        "znear": 0.01
+      }
+    },
+    {
+      "type": "orthographic",
+      "orthographic": {
+        "xmag": 1.0,
+        "ymag": 1.0,
+        "znear": 0.01
+      }
     }
    ],
    "samplers": [
index 962e475..b9d85b3 100644 (file)
                 {\r
                     "attributes": {\r
                         "POSITION": 0,\r
-                        "NORMAL": 1,\r
                         "TEXCOORD_0": 2\r
                     },\r
                     "indices": 3,\r
diff --git a/automated-tests/resources/EnvironmentTest_images/roughness_metallic_0.jpg b/automated-tests/resources/EnvironmentTest_images/roughness_metallic_0.jpg
new file mode 100644 (file)
index 0000000..8ba03d3
Binary files /dev/null and b/automated-tests/resources/EnvironmentTest_images/roughness_metallic_0.jpg differ
diff --git a/automated-tests/resources/EnvironmentTest_images/roughness_metallic_1.jpg b/automated-tests/resources/EnvironmentTest_images/roughness_metallic_1.jpg
new file mode 100644 (file)
index 0000000..d695618
Binary files /dev/null and b/automated-tests/resources/EnvironmentTest_images/roughness_metallic_1.jpg differ
index 4b454c9..5af964f 100755 (executable)
@@ -11,6 +11,7 @@ SET(TC_SOURCES
   utc-Dali-Hash.cpp
   utc-Dali-JsonReader.cpp
   utc-Dali-JsonUtil.cpp
+  utc-Dali-ModelCacheManager.cpp
 )
 
 # List of test harness files (Won't get parsed for test cases)
diff --git a/automated-tests/src/dali-scene3d-internal/utc-Dali-ModelCacheManager.cpp b/automated-tests/src/dali-scene3d-internal/utc-Dali-ModelCacheManager.cpp
new file mode 100644 (file)
index 0000000..6a8e25d
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * 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-toolkit-test-suite-utils.h>
+#include <dali-toolkit/dali-toolkit.h>
+#include <dali-scene3d/internal/common/model-cache-manager.h>
+#include <dali-scene3d/public-api/controls/model/model.h>
+#include "dali-scene3d/public-api/loader/resource-bundle.h"
+#include "dali-scene3d/public-api/loader/scene-definition.h"
+#include <toolkit-event-thread-callback.h>
+#include <string>
+
+using namespace Dali;
+using namespace Dali::Toolkit;
+using namespace Dali::Scene3D::Internal;
+
+namespace
+{
+/**
+ * For the AnimatedCube.gltf and its Assets
+ * Donated by Norbert Nopper for glTF testing.
+ * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/AnimatedCube
+ */
+const char* TEST_GLTF_FILE_NAME = TEST_RESOURCE_DIR "/AnimatedCube.gltf";
+
+static bool gResourceReadyCalled = false;
+void OnResourceReady(Control control)
+{
+  gResourceReadyCalled = true;
+}
+}
+
+int UtcDaliModelCacheManagerLoadModel(void)
+{
+  ToolkitTestApplication application;
+
+  ModelCacheManager cacheManager = ModelCacheManager::Get();
+  DALI_TEST_EQUALS(cacheManager.GetModelCacheRefCount(TEST_GLTF_FILE_NAME), 0u, TEST_LOCATION);
+
+  // Load the first instance of the same model and add it to the scene
+  Scene3D::Model model1 = Scene3D::Model::New(TEST_GLTF_FILE_NAME);
+  application.GetScene().Add(model1);
+
+  gResourceReadyCalled = false;
+  model1.ResourceReadySignal().Connect(&OnResourceReady);
+  DALI_TEST_EQUALS(gResourceReadyCalled, false, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  // Check that the loading has finished for mode1
+  DALI_TEST_EQUALS(gResourceReadyCalled, true, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(cacheManager.GetModelCacheRefCount(TEST_GLTF_FILE_NAME), 1u, TEST_LOCATION);
+  DALI_TEST_EQUALS(cacheManager.IsSceneLoading(TEST_GLTF_FILE_NAME), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(cacheManager.IsSceneLoaded(TEST_GLTF_FILE_NAME), true, TEST_LOCATION);
+
+  // Load the second instance of the same model and add it to the scene
+  Scene3D::Model model2 = Scene3D::Model::New(TEST_GLTF_FILE_NAME);
+  application.GetScene().Add(model2);
+
+  gResourceReadyCalled = false;
+  model2.ResourceReadySignal().Connect(&OnResourceReady);
+  DALI_TEST_EQUALS(gResourceReadyCalled, false, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  // Check that the loading has finished for model2
+  DALI_TEST_EQUALS(gResourceReadyCalled, true, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(cacheManager.GetModelCacheRefCount(TEST_GLTF_FILE_NAME), 2u, TEST_LOCATION);
+  DALI_TEST_EQUALS(cacheManager.IsSceneLoading(TEST_GLTF_FILE_NAME), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(cacheManager.IsSceneLoaded(TEST_GLTF_FILE_NAME), true, TEST_LOCATION);
+
+  Actor meshActor1 = model1.FindChildByName("AnimatedCube");
+  Actor meshActor2 = model2.FindChildByName("AnimatedCube");
+  DALI_TEST_CHECK(meshActor1);
+  DALI_TEST_CHECK(meshActor2);
+
+  Renderer renderer1 = meshActor1.GetRendererAt(0u);
+  Renderer renderer2 = meshActor2.GetRendererAt(0u);
+  DALI_TEST_CHECK(renderer1);
+  DALI_TEST_CHECK(renderer2);
+
+  // Check that the two instances use the shared textures and geometries from the cache
+  DALI_TEST_EQUALS(renderer1.GetTextures(), renderer2.GetTextures(), TEST_LOCATION);
+  DALI_TEST_EQUALS(renderer1.GetGeometry(), renderer2.GetGeometry(), TEST_LOCATION);
+
+  // Destroy model1
+  model1.Unparent();
+  model1.Reset();
+
+  application.SendNotification();
+  application.Render();
+
+  // Check that the reference count of the cmodel cache is decreased by 1 after model1 is destroyed
+  DALI_TEST_EQUALS(cacheManager.GetModelCacheRefCount(TEST_GLTF_FILE_NAME), 1u, TEST_LOCATION);
+
+  // Load another instance of the same model and add it to the scene
+  Scene3D::Model model3 = Scene3D::Model::New(TEST_GLTF_FILE_NAME);
+  application.GetScene().Add(model3);
+
+  gResourceReadyCalled = false;
+  model3.ResourceReadySignal().Connect(&OnResourceReady);
+  DALI_TEST_EQUALS(gResourceReadyCalled, false, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  // Check that the loading has finished for model3
+  DALI_TEST_EQUALS(gResourceReadyCalled, true, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(cacheManager.GetModelCacheRefCount(TEST_GLTF_FILE_NAME), 2u, TEST_LOCATION);
+  DALI_TEST_EQUALS(cacheManager.IsSceneLoading(TEST_GLTF_FILE_NAME), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(cacheManager.IsSceneLoaded(TEST_GLTF_FILE_NAME), true, TEST_LOCATION);
+
+  Actor meshActor3 = model3.FindChildByName("AnimatedCube");
+  DALI_TEST_CHECK(meshActor3);
+
+  Renderer renderer3 = meshActor3.GetRendererAt(0u);
+  DALI_TEST_CHECK(renderer3);
+
+  // Check that model2 and model3 use the shared textures and geometries from the cache
+  DALI_TEST_EQUALS(renderer2.GetTextures(), renderer3.GetTextures(), TEST_LOCATION);
+  DALI_TEST_EQUALS(renderer2.GetGeometry(), renderer3.GetGeometry(), TEST_LOCATION);
+
+  // Destroy model2 and model3
+  model2.Unparent();
+  model2.Reset();
+
+  model3.Unparent();
+  model3.Reset();
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(cacheManager.GetModelCacheRefCount(TEST_GLTF_FILE_NAME), 0u, TEST_LOCATION);
+
+  END_TEST;
+}
+
index 5fec8f2..83f345c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
@@ -35,7 +35,7 @@ int UtcDaliCameraParameters(void)
                                           Vector3::ZAXIS * -100.f);
   camParams.orthographicSize = 3.0f;
   camParams.aspectRatio      = 1.0f;
-  camParams.yFov             = Degree(Radian(M_PI * .5)).degree;
+  camParams.yFovDegree       = Degree(Radian(M_PI * .5));
   camParams.zNear            = 1.f;
   camParams.zFar             = 1000.f;
 
@@ -70,7 +70,7 @@ int UtcDaliCameraParameters(void)
 
     if(camParams.isPerspective)
     {
-      DALI_TEST_EQUAL(camera.GetProperty(Dali::CameraActor::Property::FIELD_OF_VIEW).Get<float>(), Radian(Degree(camParams.yFov)).radian);
+      DALI_TEST_EQUAL(camera.GetProperty(Dali::CameraActor::Property::FIELD_OF_VIEW).Get<float>(), Radian(camParams.yFovDegree).radian);
     }
     else
     {
index a485f0f..bcb61b5 100644 (file)
@@ -179,7 +179,7 @@ int UtcDaliGltfLoaderSuccess1(void)
   LoadGltfScene(TEST_RESOURCE_DIR "/AnimatedCube.gltf", sdf, ctx.loadResult);
 
   DALI_TEST_EQUAL(1u, ctx.scene.GetRoots().size());
-  DALI_TEST_EQUAL(6u, ctx.scene.GetNodeCount());
+  DALI_TEST_EQUAL(9u, ctx.scene.GetNodeCount());
 
   // Default envmap is used
   DALI_TEST_EQUAL(1u, ctx.resources.mEnvironmentMaps.size());
@@ -452,7 +452,7 @@ int UtcDaliGltfLoaderSuccess1(void)
   DALI_TEST_EQUAL(2u, ctx.resources.mShaders.size());
   DALI_TEST_EQUAL(0u, ctx.resources.mSkeletons.size());
 
-  DALI_TEST_EQUAL(3u, ctx.cameras.size());
+  DALI_TEST_EQUAL(6u, ctx.cameras.size());
   DALI_TEST_EQUAL(0u, ctx.lights.size());
   DALI_TEST_EQUAL(1u, ctx.animations.size());
   DALI_TEST_EQUAL(0u, ctx.animationGroups.size());
index 5d2c344..7840c74 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
@@ -26,6 +26,8 @@
 
 #include <dali-scene3d/public-api/controls/model/model.h>
 
+#include <dali/devel-api/actors/camera-actor-devel.h>
+
 using namespace Dali;
 using namespace Dali::Toolkit;
 
@@ -52,13 +54,18 @@ const char* TEST_GLTF_FILE_NAME                    = TEST_RESOURCE_DIR "/Animate
 const char* TEST_GLTF_ANIMATION_TEST_FILE_NAME     = TEST_RESOURCE_DIR "/animationTest.gltf";
 const char* TEST_GLTF_MULTIPLE_PRIMITIVE_FILE_NAME = TEST_RESOURCE_DIR "/simpleMultiplePrimitiveTest.gltf";
 const char* TEST_DLI_FILE_NAME                     = TEST_RESOURCE_DIR "/arc.dli";
-const char* TEST_DLI_EXERCISE_FILE_NAME            = TEST_RESOURCE_DIR "/exercise.dli";
+// @TODO: The test cases for loading the DLI model below is temporarily disabled.
+// Need to fix how resources are loaded when a model contains multiple scenes and
+// each scene has its own root node.
+#ifdef MULTIPLE_SCENES_MODEL_SUPPORT
+const char* TEST_DLI_EXERCISE_FILE_NAME = TEST_RESOURCE_DIR "/exercise.dli";
+#endif
 /**
  * For the diffuse and specular cube map texture.
  * These textures are based off version of Wave engine sample
  * Take from https://github.com/WaveEngine/Samples
  *
- * Copyright (c) 2022 Wave Coorporation
+ * Copyright (c) 2023 Wave Coorporation
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to
@@ -411,8 +418,9 @@ int UtcDaliModelSetImageBasedLightSource01(void)
 
   DALI_TEST_EQUALS(gResourceReadyCalled, true, TEST_LOCATION);
 
-  Texture newDiffuseTexture  = textureSet.GetTexture(7u);
-  Texture newSpecularTexture = textureSet.GetTexture(8u);
+  TextureSet newTextureSet      = renderer.GetTextures();
+  Texture    newDiffuseTexture  = newTextureSet.GetTexture(7u);
+  Texture    newSpecularTexture = newTextureSet.GetTexture(8u);
 
   DALI_TEST_NOT_EQUALS(diffuseTexture, newDiffuseTexture, 0.0f, TEST_LOCATION);
   DALI_TEST_NOT_EQUALS(specularTexture, newSpecularTexture, 0.0f, TEST_LOCATION);
@@ -1003,6 +1011,7 @@ int UtcDaliModelAnimation02(void)
 
 int UtcDaliModelAnimation03(void)
 {
+#ifdef MULTIPLE_SCENES_MODEL_SUPPORT
   ToolkitTestApplication application;
 
   Scene3D::Model model = Scene3D::Model::New(TEST_DLI_EXERCISE_FILE_NAME);
@@ -1031,6 +1040,121 @@ int UtcDaliModelAnimation03(void)
   Animation animationByName = model.GetAnimation("idleClip");
   DALI_TEST_CHECK(animationByName);
   DALI_TEST_EQUALS(animationByIndex, animationByName, TEST_LOCATION);
+#else
+  tet_result(TET_PASS);
+#endif
+
+  END_TEST;
+}
+
+int UtcDaliModelCameraGenerate01(void)
+{
+#ifdef MULTIPLE_SCENES_MODEL_SUPPORT
+  ToolkitTestApplication application;
+
+  Scene3D::Model model = Scene3D::Model::New(TEST_DLI_EXERCISE_FILE_NAME);
+  model.SetProperty(Dali::Actor::Property::SIZE, Vector2(50, 50));
+  application.GetScene().Add(model);
+
+  gResourceReadyCalled = false;
+  model.ResourceReadySignal().Connect(&OnResourceReady);
+  DALI_TEST_EQUALS(gResourceReadyCalled, false, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(gResourceReadyCalled, true, TEST_LOCATION);
+
+  uint32_t cameraCount = model.GetCameraCount();
+  DALI_TEST_EQUALS(1, cameraCount, TEST_LOCATION);
+
+  CameraActor generatedCamera = model.GenerateCamera(0u);
+  DALI_TEST_CHECK(generatedCamera);
+
+  generatedCamera = model.GenerateCamera(1u); // Fail to generate camera
+  DALI_TEST_CHECK(!generatedCamera);
+#else
+  tet_result(TET_PASS);
+#endif
+
+  END_TEST;
+}
+
+int UtcDaliModelCameraGenerate02(void)
+{
+  ToolkitTestApplication application;
+
+  Scene3D::Model model = Scene3D::Model::New(TEST_GLTF_FILE_NAME);
+  model.SetProperty(Dali::Actor::Property::SIZE, Vector2(50, 50));
+  application.GetScene().Add(model);
+
+  gResourceReadyCalled = false;
+  model.ResourceReadySignal().Connect(&OnResourceReady);
+  DALI_TEST_EQUALS(gResourceReadyCalled, false, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(gResourceReadyCalled, true, TEST_LOCATION);
+
+  uint32_t cameraCount = model.GetCameraCount();
+  DALI_TEST_EQUALS(6, cameraCount, TEST_LOCATION);
+
+  CameraActor generatedCamera0 = model.GenerateCamera(0u);
+  DALI_TEST_CHECK(generatedCamera0);
+  CameraActor generatedCamera1 = model.GenerateCamera(1u);
+  DALI_TEST_CHECK(generatedCamera1);
+  CameraActor generatedCamera2 = model.GenerateCamera(2u);
+  DALI_TEST_CHECK(generatedCamera2);
+  CameraActor generatedCamera3 = model.GenerateCamera(3u); // Infinity far camera
+  DALI_TEST_CHECK(generatedCamera3);
+  CameraActor generatedCamera4 = model.GenerateCamera(4u); // Broken camera 1
+  DALI_TEST_CHECK(!generatedCamera4);
+  CameraActor generatedCamera5 = model.GenerateCamera(5u); // Broken camera 2
+  DALI_TEST_CHECK(!generatedCamera5);
+  CameraActor generatedCamera6 = model.GenerateCamera(6u); // Out of bound
+  DALI_TEST_CHECK(!generatedCamera6);
+
+  CameraActor appliedCamera;
+  DALI_TEST_EQUALS(model.ApplyCamera(0u, appliedCamera), false, TEST_LOCATION); // Cannot apply into empty camera.
+
+  auto CompareCameraProperties = [](CameraActor lhs, CameraActor rhs, const char* location) {
+    DALI_TEST_EQUALS(lhs.GetProperty<int>(Dali::CameraActor::Property::PROJECTION_MODE), rhs.GetProperty<int>(Dali::CameraActor::Property::PROJECTION_MODE), TEST_LOCATION);
+    DALI_TEST_EQUALS(lhs.GetProperty<float>(Dali::CameraActor::Property::NEAR_PLANE_DISTANCE), rhs.GetProperty<float>(Dali::CameraActor::Property::NEAR_PLANE_DISTANCE), TEST_LOCATION);
+
+    if(lhs.GetProperty<int>(Dali::CameraActor::Property::PROJECTION_MODE) == static_cast<int>(Dali::Camera::ProjectionMode::PERSPECTIVE_PROJECTION))
+    {
+      DALI_TEST_EQUALS(lhs.GetProperty<float>(Dali::CameraActor::Property::FIELD_OF_VIEW), rhs.GetProperty<float>(Dali::CameraActor::Property::FIELD_OF_VIEW), TEST_LOCATION);
+      // TODO : Open this test when infinity far projection implement.
+      //DALI_TEST_EQUALS(lhs.GetProperty<float>(Dali::CameraActor::Property::FAR_PLANE_DISTANCE), rhs.GetProperty<float>(Dali::CameraActor::Property::FAR_PLANE_DISTANCE), TEST_LOCATION);
+    }
+    else
+    {
+      DALI_TEST_EQUALS(lhs.GetProperty<float>(Dali::DevelCameraActor::Property::ORTHOGRAPHIC_SIZE), rhs.GetProperty<float>(Dali::DevelCameraActor::Property::ORTHOGRAPHIC_SIZE), TEST_LOCATION);
+      DALI_TEST_EQUALS(lhs.GetProperty<float>(Dali::CameraActor::Property::FAR_PLANE_DISTANCE), rhs.GetProperty<float>(Dali::CameraActor::Property::FAR_PLANE_DISTANCE), TEST_LOCATION);
+    }
+  };
+
+  appliedCamera = CameraActor::New();
+  DALI_TEST_EQUALS(model.ApplyCamera(0u, appliedCamera), true, TEST_LOCATION);
+  CompareCameraProperties(generatedCamera0, appliedCamera, TEST_LOCATION);
+  DALI_TEST_EQUALS(model.ApplyCamera(1u, appliedCamera), true, TEST_LOCATION);
+  CompareCameraProperties(generatedCamera1, appliedCamera, TEST_LOCATION);
+  DALI_TEST_EQUALS(model.ApplyCamera(2u, appliedCamera), true, TEST_LOCATION);
+  CompareCameraProperties(generatedCamera2, appliedCamera, TEST_LOCATION);
+  DALI_TEST_EQUALS(model.ApplyCamera(3u, appliedCamera), true, TEST_LOCATION);
+  CompareCameraProperties(generatedCamera3, appliedCamera, TEST_LOCATION);
+  DALI_TEST_EQUALS(model.ApplyCamera(4u, appliedCamera), false, TEST_LOCATION); // Broken camera 1
+  DALI_TEST_EQUALS(model.ApplyCamera(5u, appliedCamera), false, TEST_LOCATION); // Broken camera 2
+  DALI_TEST_EQUALS(model.ApplyCamera(6u, appliedCamera), false, TEST_LOCATION); // Cannot apply over the index.
 
   END_TEST;
 }
@@ -1082,8 +1206,8 @@ int UtcDaliModelColorMode(void)
   application.SendNotification();
   application.Render();
 
-  Actor actor = model.FindChildByName("AnimatedCube");
-  Vector4 childColor = actor[Dali::Actor::Property::COLOR];
+  Actor   actor           = model.FindChildByName("AnimatedCube");
+  Vector4 childColor      = actor[Dali::Actor::Property::COLOR];
   Vector4 childWorldColor = actor[Dali::Actor::Property::WORLD_COLOR];
 
   DALI_TEST_EQUALS(childColor, Color::WHITE, TEST_LOCATION);
@@ -1091,6 +1215,7 @@ int UtcDaliModelColorMode(void)
 
   END_TEST;
 }
+
 int UtcDaliModelResourceReady(void)
 {
   ToolkitTestApplication application;
@@ -1122,3 +1247,106 @@ int UtcDaliModelResourceReady(void)
 
   END_TEST;
 }
+
+int UtcDaliModelResourceCacheCheck(void)
+{
+  ToolkitTestApplication application;
+
+  // Load three instances of the same model and add them to the scene
+  Scene3D::Model model1 = Scene3D::Model::New(TEST_GLTF_FILE_NAME);
+  Scene3D::Model model2 = Scene3D::Model::New(TEST_GLTF_FILE_NAME);
+  Scene3D::Model model3 = Scene3D::Model::New(TEST_GLTF_FILE_NAME);
+
+  application.GetScene().Add(model1);
+  application.GetScene().Add(model2);
+  application.GetScene().Add(model3);
+
+  gResourceReadyCalled = false;
+  model1.ResourceReadySignal().Connect(&OnResourceReady);
+  model2.ResourceReadySignal().Connect(&OnResourceReady);
+  model3.ResourceReadySignal().Connect(&OnResourceReady);
+  DALI_TEST_EQUALS(gResourceReadyCalled, false, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(3), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  // Check that the loading has finished for all the three instances
+  DALI_TEST_EQUALS(gResourceReadyCalled, true, TEST_LOCATION);
+
+  Actor meshActor1 = model1.FindChildByName("AnimatedCube");
+  Actor meshActor2 = model2.FindChildByName("AnimatedCube");
+  Actor meshActor3 = model3.FindChildByName("AnimatedCube");
+  DALI_TEST_CHECK(meshActor1);
+  DALI_TEST_CHECK(meshActor2);
+  DALI_TEST_CHECK(meshActor3);
+
+  Renderer renderer1 = meshActor1.GetRendererAt(0u);
+  Renderer renderer2 = meshActor2.GetRendererAt(0u);
+  Renderer renderer3 = meshActor3.GetRendererAt(0u);
+  DALI_TEST_CHECK(renderer1);
+  DALI_TEST_CHECK(renderer2);
+  DALI_TEST_CHECK(renderer3);
+
+  // Check that all the three instances use the shared textures and geometries from the cache
+  // but have their own shader objects
+  DALI_TEST_EQUALS(renderer1.GetTextures(), renderer2.GetTextures(), TEST_LOCATION);
+  DALI_TEST_EQUALS(renderer1.GetTextures(), renderer3.GetTextures(), TEST_LOCATION);
+  DALI_TEST_EQUALS(renderer1.GetGeometry(), renderer2.GetGeometry(), TEST_LOCATION);
+  DALI_TEST_EQUALS(renderer1.GetGeometry(), renderer3.GetGeometry(), TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(renderer1.GetShader(), renderer2.GetShader(), 0.0f, TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(renderer1.GetShader(), renderer3.GetShader(), 0.0f, TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(renderer2.GetShader(), renderer3.GetShader(), 0.0f, TEST_LOCATION);
+
+  // Destroy model1
+  model1.Unparent();
+  model1.Reset();
+
+  // Check that all the other two instances still use the shared textures and geometries from the cache
+  // but have their own shader objects
+  DALI_TEST_EQUALS(renderer2.GetTextures(), renderer3.GetTextures(), TEST_LOCATION);
+  DALI_TEST_EQUALS(renderer2.GetGeometry(), renderer3.GetGeometry(), TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(renderer2.GetShader(), renderer3.GetShader(), 0.0f, TEST_LOCATION);
+
+  // Set new IBL textures for model2, and this should apply to model2 instance only
+  gResourceReadyCalled = false;
+  DALI_TEST_EQUALS(gResourceReadyCalled, false, TEST_LOCATION);
+  model2.SetImageBasedLightSource(TEST_DIFFUSE_TEXTURE, TEST_SPECULAR_TEXTURE);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  // Check that the new IBL textures are loaded for model2
+  DALI_TEST_EQUALS(gResourceReadyCalled, true, TEST_LOCATION);
+
+  // Check that the two instances still use the shared geometries from the cache
+  // but now have their own shader objects and different texture set
+  DALI_TEST_NOT_EQUALS(renderer2.GetTextures(), renderer3.GetTextures(), 0.0f, TEST_LOCATION);
+  DALI_TEST_EQUALS(renderer2.GetGeometry(), renderer3.GetGeometry(), TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(renderer2.GetShader(), renderer3.GetShader(), 0.0f, TEST_LOCATION);
+
+  // Check that the two instances now have their own diffuse texture and specular texture,
+  // but all the other textures are still the same
+  TextureSet textureSet2 = renderer2.GetTextures();
+  TextureSet textureSet3 = renderer3.GetTextures();
+  DALI_TEST_EQUALS(textureSet2.GetTextureCount(), 9u, TEST_LOCATION);
+  DALI_TEST_EQUALS(textureSet3.GetTextureCount(), 9u, TEST_LOCATION);
+
+  for (uint32_t i = 0; i < 7u; i++)
+  {
+    DALI_TEST_EQUALS(textureSet2.GetTexture(i), textureSet3.GetTexture(i), TEST_LOCATION);
+  }
+
+  DALI_TEST_NOT_EQUALS(textureSet2.GetTexture(7u), textureSet3.GetTexture(7u), 0.0f, TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(textureSet2.GetTexture(8u), textureSet3.GetTexture(8u), 0.0f, TEST_LOCATION);
+
+  END_TEST;
+}
+
index 50b41cc..caaca31 100644 (file)
@@ -458,8 +458,8 @@ int UtcDaliSceneViewImageBasedLight01(void)
   application.SendNotification();
   application.Render();
 
-  DALI_TEST_NOT_EQUALS(GetDiffuseTexture(modelView1), GetDiffuseTexture(modelView2), 0.0f, TEST_LOCATION);
-  DALI_TEST_NOT_EQUALS(GetSpecularTexture(modelView1), GetSpecularTexture(modelView2), 0.0f, TEST_LOCATION);
+  DALI_TEST_EQUALS(GetDiffuseTexture(modelView1), GetDiffuseTexture(modelView2), TEST_LOCATION);
+  DALI_TEST_EQUALS(GetSpecularTexture(modelView1), GetSpecularTexture(modelView2), TEST_LOCATION);
   DALI_TEST_NOT_EQUALS(GetDiffuseTexture(modelView1), GetDiffuseTexture(modelView3), 0.0f, TEST_LOCATION);
   DALI_TEST_NOT_EQUALS(GetSpecularTexture(modelView1), GetSpecularTexture(modelView3), 0.0f, TEST_LOCATION);
 
diff --git a/dali-scene3d/internal/common/model-cache-manager.cpp b/dali-scene3d/internal/common/model-cache-manager.cpp
new file mode 100644 (file)
index 0000000..01d2eba
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * 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/common/model-cache-manager.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/common/singleton-service.h>
+#include <dali/public-api/object/base-object.h>
+#include <unordered_map>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/public-api/loader/load-result.h>
+#include <dali-scene3d/public-api/loader/scene-definition.h>
+
+namespace Dali::Scene3D::Internal
+{
+class ModelCacheManager::Impl : public Dali::BaseObject
+{
+public:
+  /**
+   * @brief Constructor
+   */
+  Impl()
+  {
+  }
+
+  Dali::Scene3D::Loader::LoadResult GetModelLoadResult(std::string modelUri)
+  {
+    ModelCache& cache = mModelCache[modelUri];
+    return Dali::Scene3D::Loader::LoadResult{cache.resources, cache.scene, cache.metaData, cache.animationDefinitions, cache.amimationGroupDefinitions, cache.cameraParameters, cache.lights};
+  }
+
+  uint32_t GetModelCacheRefCount(std::string modelUri)
+  {
+    ModelCache& cache = mModelCache[modelUri];
+    return cache.refCount;
+  }
+
+  Dali::ConditionalWait& GetLoadSceneConditionalWaitInstance(std::string modelUri)
+  {
+    ModelCache& cache = mModelCache[modelUri];
+    return cache.loadSceneConditionalWait;
+  }
+
+  Dali::ConditionalWait& GetLoadRawResourceConditionalWaitInstance(std::string modelUri)
+  {
+    ModelCache& cache = mModelCache[modelUri];
+    return cache.loadRawResourceConditionalWait;
+  }
+
+  void ReferenceModelCache(std::string modelUri)
+  {
+    ModelCache& cache = mModelCache[modelUri];
+    cache.refCount++;
+  }
+
+  void UnreferenceModelCache(std::string modelUri)
+  {
+    ModelCache& cache = mModelCache[modelUri];
+    if(cache.refCount > 0)
+    {
+      cache.refCount--;
+    }
+
+    if(cache.refCount == 0)
+    {
+      mModelCache.erase(modelUri);
+    }
+  }
+
+  bool IsSceneLoaded(std::string modelUri)
+  {
+    ModelCache& cache = mModelCache[modelUri];
+    return cache.isSceneLoaded;
+  }
+
+  void SetSceneLoaded(std::string modelUri, bool isSceneLoaded)
+  {
+    ModelCache& cache   = mModelCache[modelUri];
+    cache.isSceneLoaded = isSceneLoaded;
+  }
+
+  bool IsSceneLoading(std::string modelUri)
+  {
+    ModelCache& cache = mModelCache[modelUri];
+    return cache.isSceneLoading;
+  }
+
+  void SetSceneLoading(std::string modelUri, bool isSceneLoading)
+  {
+    ModelCache& cache    = mModelCache[modelUri];
+    cache.isSceneLoading = isSceneLoading;
+  }
+
+protected:
+  /**
+   * A reference counted object may only be deleted by calling Unreference()
+   */
+  virtual ~Impl()
+  {
+  }
+
+private:
+  struct ModelCache
+  {
+    Dali::Scene3D::Loader::ResourceBundle  resources{}; ///< The bundle to store resources in.
+    Dali::Scene3D::Loader::SceneDefinition scene{};     ///< The scene definition to populate.
+    Dali::Scene3D::Loader::SceneMetadata   metaData{};  ///< The metadata of the scene.
+
+    std::vector<Dali::Scene3D::Loader::AnimationDefinition>      animationDefinitions{};      ///< The list of animation definitions, in lexicographical order of their names.
+    std::vector<Dali::Scene3D::Loader::AnimationGroupDefinition> amimationGroupDefinitions{}; ///< The list of animation group definitions, in lexicographical order of their names.
+    std::vector<Dali::Scene3D::Loader::CameraParameters>         cameraParameters{};          ///< The camera parameters that were loaded from the scene.
+    std::vector<Dali::Scene3D::Loader::LightParameters>          lights{};                    ///< The light parameters that were loaded from the scene.
+
+    uint32_t              refCount{0};                      ///< The reference count of this model cache.
+    Dali::ConditionalWait loadSceneConditionalWait{};       ///< The conditionalWait instance used to synchronise the loading of the scene for the same model in different threads.
+    Dali::ConditionalWait loadRawResourceConditionalWait{}; ///< The conditionalWait instance used to synchronise the loading of the shared raw resources for the same model in different threads.
+
+    bool isSceneLoaded{false};  ///< Whether the scene of the model has been loaded.
+    bool isSceneLoading{false}; ///< Whether the scene loading of the model is in progress.
+  };
+
+  using ModelResourceCache = std::unordered_map<std::string, ModelCache>;
+  ModelResourceCache mModelCache;
+};
+
+ModelCacheManager::ModelCacheManager() = default;
+
+ModelCacheManager::~ModelCacheManager() = default;
+
+ModelCacheManager ModelCacheManager::Get()
+{
+  ModelCacheManager manager;
+
+  // Check whether the ModelCacheManager is already created
+  SingletonService singletonService(SingletonService::Get());
+  if(singletonService)
+  {
+    Dali::BaseHandle handle = singletonService.GetSingleton(typeid(ModelCacheManager));
+    if(handle)
+    {
+      // If so, downcast the handle of singleton to ModelCacheManager
+      manager = ModelCacheManager(dynamic_cast<ModelCacheManager::Impl*>(handle.GetObjectPtr()));
+    }
+
+    if(!manager)
+    {
+      // If not, create the ModelCacheManager and register it as a singleton
+      manager = ModelCacheManager(new ModelCacheManager::Impl());
+      singletonService.Register(typeid(manager), manager);
+    }
+  }
+
+  return manager;
+}
+
+ModelCacheManager::ModelCacheManager(ModelCacheManager::Impl* impl)
+: BaseHandle(impl)
+{
+}
+
+Dali::Scene3D::Loader::LoadResult ModelCacheManager::GetModelLoadResult(std::string modelUri)
+{
+  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
+  return impl.GetModelLoadResult(modelUri);
+}
+
+uint32_t ModelCacheManager::GetModelCacheRefCount(std::string modelUri)
+{
+  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
+  return impl.GetModelCacheRefCount(modelUri);
+}
+
+Dali::ConditionalWait& ModelCacheManager::GetLoadSceneConditionalWaitInstance(std::string modelUri)
+{
+  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
+  return impl.GetLoadSceneConditionalWaitInstance(modelUri);
+}
+
+Dali::ConditionalWait& ModelCacheManager::GetLoadRawResourceConditionalWaitInstance(std::string modelUri)
+{
+  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
+  return impl.GetLoadRawResourceConditionalWaitInstance(modelUri);
+}
+
+void ModelCacheManager::ReferenceModelCache(std::string modelUri)
+{
+  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
+  impl.ReferenceModelCache(modelUri);
+}
+
+void ModelCacheManager::UnreferenceModelCache(std::string modelUri)
+{
+  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
+  impl.UnreferenceModelCache(modelUri);
+}
+
+bool ModelCacheManager::IsSceneLoaded(std::string modelUri)
+{
+  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
+  return impl.IsSceneLoaded(modelUri);
+}
+
+void ModelCacheManager::SetSceneLoaded(std::string modelUri, bool isSceneLoaded)
+{
+  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
+  impl.SetSceneLoaded(modelUri, isSceneLoaded);
+}
+
+bool ModelCacheManager::IsSceneLoading(std::string modelUri)
+{
+  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
+  return impl.IsSceneLoading(modelUri);
+}
+
+void ModelCacheManager::SetSceneLoading(std::string modelUri, bool isSceneLoading)
+{
+  ModelCacheManager::Impl& impl = static_cast<ModelCacheManager::Impl&>(GetBaseObject());
+  impl.SetSceneLoading(modelUri, isSceneLoading);
+}
+
+} // namespace Dali::Scene3D::Internal
diff --git a/dali-scene3d/internal/common/model-cache-manager.h b/dali-scene3d/internal/common/model-cache-manager.h
new file mode 100644 (file)
index 0000000..a427945
--- /dev/null
@@ -0,0 +1,159 @@
+#ifndef DALI_SCENE3D_MODEL_CACHE_MANAGER_H
+#define DALI_SCENE3D_MODEL_CACHE_MANAGER_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.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/threading/conditional-wait.h>
+#include <dali/public-api/object/base-handle.h>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/public-api/loader/load-result.h>
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Internal
+{
+class ModelCacheManager;
+
+/**
+ * A singleton class to manage the cache of 3D models so that the resources of the same model
+ * are only loaded once and kept in the cache. The cached resources will be reused when the
+ * same model is loaded multiple times.
+ */
+class ModelCacheManager : public Dali::BaseHandle
+{
+public:
+  /**
+   * @brief Creates a ModelCacheManager handle.
+   *
+   * Calling member functions with an uninitialised handle is not allowed.
+   */
+  ModelCacheManager();
+
+  /**
+   * @brief Destructor
+   *
+   * This is non-virtual since derived Handle types must not contain data or virtual methods.
+   */
+  ~ModelCacheManager();
+
+  /**
+   * @brief Create or retrieve the ModelCacheManager singleton.
+   *
+   * @return A handle to the ModelCacheManager.
+   */
+  static ModelCacheManager Get();
+
+  /**
+   * @brief Retrieves the load result for the model with the given URI.
+   * If there is no existing load result for the given model, a new one will be created.
+   * @param[in] modelUri The unique model URI with its absolute path.
+   * @return A reference to the model's load result.
+   */
+  Dali::Scene3D::Loader::LoadResult GetModelLoadResult(std::string modelUri);
+
+  /**
+   * @brief Retrieves the reference count of the cache for the model with the given URI.
+   * @param[in] modelUri The unique model URI with its absolute path.
+   * @return The reference count of the cache.
+   */
+  uint32_t GetModelCacheRefCount(std::string modelUri);
+
+  /**
+   * @brief Retrieves the ConditionalWait object to synchronize the scene loading of the model
+   * with the given URI between multiple threads.
+   * @param[in] modelUri The unique model URI with its absolute path.
+   * @return The ConditionalWait object.
+   */
+  Dali::ConditionalWait& GetLoadSceneConditionalWaitInstance(std::string modelUri);
+
+  /**
+   * @brief Retrieves the ConditionalWait object to synchronize the raw resources loading of the
+   * model with the given URI between multiple threads.
+   * @param[in] modelUri The unique model URI with its absolute path.
+   * @return The ConditionalWait object.
+   */
+  Dali::ConditionalWait& GetLoadRawResourceConditionalWaitInstance(std::string modelUri);
+
+  /**
+   * @brief Reference the cache of the model with the given URI.
+   * This will increment the reference count of the load result by 1.
+   * @param[in] modelUri The model URI.
+   */
+  void ReferenceModelCache(std::string modelUri);
+
+  /**
+   * @brief Unreference the cache of the model with the given URI.
+   * This will decrement the reference count of the load result by 1.
+   * When the reference count becomes zero, the model will be removed from the cache and all
+   * its resources will be deleted.
+   * @param[in] modelUri The model URI.
+   */
+  void UnreferenceModelCache(std::string modelUri);
+
+  /**
+   * @brief Retrieves whether the scene of the model with the given URI has been loaded.
+   * @param[in] modelUri The unique model URI with its absolute path.
+   * @return whether the scene of the model has been loaded. This will be true if the scene
+   * has been loaded for once.
+   */
+  bool IsSceneLoaded(std::string modelUri);
+
+  /**
+   * @brief Sets whether the scene of the model with the given URI has been loaded.
+   * @param[in] modelUri The unique model URI with its absolute path.
+   * @param[in] isSceneLoaded Whether the scene of the model has been loaded.
+   */
+  void SetSceneLoaded(std::string modelUri, bool isSceneLoaded);
+
+  /**
+   * @brief Retrieves whether the scene loading of the model with the given URI is in progress.
+   * @param[in] modelUri The unique model URI with its absolute path.
+   * @return whether the scene loading of the model is in progress.
+   */
+  bool IsSceneLoading(std::string modelUri);
+
+  /**
+   * @brief Sets whether the scene loading of the model with the given URI is in progress.
+   * @param[in] modelUri The unique model URI with its absolute path.
+   * @param[in] isSceneLoading Whether the scene loading of the model is in progress.
+   */
+  void SetSceneLoading(std::string modelUri, bool isSceneLoading);
+
+public:
+  // Default copy and move operator
+  ModelCacheManager(const ModelCacheManager& rhs) = default;
+  ModelCacheManager(ModelCacheManager&& rhs)      = default;
+  ModelCacheManager& operator=(const ModelCacheManager& rhs) = default;
+  ModelCacheManager& operator=(ModelCacheManager&& rhs) = default;
+
+private:
+  class Impl;
+  explicit DALI_INTERNAL ModelCacheManager(ModelCacheManager::Impl* impl);
+};
+
+} // namespace Internal
+
+} // namespace Scene3D
+
+} // namespace Dali
+
+#endif // DALI_SCENE3D_MODEL_CACHE_MANAGER_H
index c5d97f9..aaec429 100644 (file)
 #include <dali-scene3d/internal/common/model-load-task.h>
 
 // EXTERNAL INCLUDES
-#include <filesystem>
 #include <dali/integration-api/debug.h>
+#include <filesystem>
 
 // INTERNAL INCLUDES
+#include <dali-scene3d/internal/common/model-cache-manager.h>
 #include <dali-scene3d/public-api/loader/animation-definition.h>
 #include <dali-scene3d/public-api/loader/camera-parameters.h>
 #include <dali-scene3d/public-api/loader/dli-loader.h>
@@ -31,7 +32,6 @@
 #include <dali-scene3d/public-api/loader/node-definition.h>
 #include <dali-scene3d/public-api/loader/shader-definition-factory.h>
 
-
 namespace Dali
 {
 namespace Scene3D
@@ -52,7 +52,9 @@ ModelLoadTask::ModelLoadTask(const std::string& modelUrl, const std::string& res
 : AsyncTask(callback),
   mModelUrl(modelUrl),
   mResourceDirectoryUrl(resourceDirectoryUrl),
-  mHasSucceeded(false)
+  mHasSucceeded(false),
+  mModelCacheManager(ModelCacheManager::Get()),
+  mLoadResult(mModelCacheManager.GetModelLoadResult(modelUrl))
 {
 }
 
@@ -62,6 +64,10 @@ ModelLoadTask::~ModelLoadTask()
 
 void ModelLoadTask::Process()
 {
+  uint32_t               cacheRefCount                  = mModelCacheManager.GetModelCacheRefCount(mModelUrl);
+  Dali::ConditionalWait& loadSceneConditionalWait       = mModelCacheManager.GetLoadSceneConditionalWaitInstance(mModelUrl);
+  Dali::ConditionalWait& loadRawResourceConditionalWait = mModelCacheManager.GetLoadRawResourceConditionalWaitInstance(mModelUrl);
+
   std::filesystem::path modelUrl(mModelUrl);
   if(mResourceDirectoryUrl.empty())
   {
@@ -70,63 +76,112 @@ void ModelLoadTask::Process()
   std::string extension = modelUrl.extension();
   std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
 
-  Dali::Scene3D::Loader::ResourceBundle::PathProvider pathProvider = [&](Dali::Scene3D::Loader::ResourceType::Value type)
-  {
+  Dali::Scene3D::Loader::ResourceBundle::PathProvider pathProvider = [&](Dali::Scene3D::Loader::ResourceType::Value type) {
     return mResourceDirectoryUrl;
   };
-  mAnimations.clear();
-
-  std::filesystem::path metaDataUrl = modelUrl;
-  metaDataUrl.replace_extension(METADATA_EXTENSION.data());
-
-  Dali::Scene3D::Loader::LoadSceneMetadata(metaDataUrl.c_str(), mMetaData);
 
-  Dali::Scene3D::Loader::LoadResult result{mResources, mScene, mMetaData, mAnimations, mAnimGroups, mCameraParameters, mLights};
-
-  if(extension == DLI_EXTENSION)
   {
-    Dali::Scene3D::Loader::DliLoader              loader;
-    Dali::Scene3D::Loader::DliLoader::InputParams input{
-      pathProvider(Dali::Scene3D::Loader::ResourceType::Mesh),
-      nullptr,
-      {},
-      {},
-      nullptr,
-      {}};
-    Dali::Scene3D::Loader::DliLoader::LoadParams loadParams{input, result};
-    if(!loader.LoadScene(mModelUrl, loadParams))
+    ConditionalWait::ScopedLock lock(loadSceneConditionalWait);
+
+    while(cacheRefCount > 1 && mModelCacheManager.IsSceneLoading(mModelUrl))
     {
-      DALI_LOG_ERROR("Failed to load scene from '%s': %s\n", mModelUrl.c_str(), loader.GetParseError().c_str());
-      return;
+      loadSceneConditionalWait.Wait();
     }
   }
-  else if(extension == GLTF_EXTENSION)
+
   {
-    Dali::Scene3D::Loader::ShaderDefinitionFactory sdf;
-    sdf.SetResources(mResources);
-    Dali::Scene3D::Loader::LoadGltfScene(mModelUrl, sdf, result);
+    ConditionalWait::ScopedLock lock(loadSceneConditionalWait);
+
+    if(!mModelCacheManager.IsSceneLoaded(mModelUrl))
+    {
+      mModelCacheManager.SetSceneLoading(mModelUrl, true);
+
+      std::filesystem::path metaDataUrl = modelUrl;
+      metaDataUrl.replace_extension(METADATA_EXTENSION.data());
+
+      Dali::Scene3D::Loader::LoadSceneMetadata(metaDataUrl.c_str(), mLoadResult.mSceneMetadata);
+
+      mLoadResult.mAnimationDefinitions.clear();
+
+      if(extension == DLI_EXTENSION)
+      {
+        Dali::Scene3D::Loader::DliLoader              loader;
+        Dali::Scene3D::Loader::DliLoader::InputParams input{
+          pathProvider(Dali::Scene3D::Loader::ResourceType::Mesh),
+          nullptr,
+          {},
+          {},
+          nullptr,
+          {}};
+        Dali::Scene3D::Loader::DliLoader::LoadParams loadParams{input, mLoadResult};
+        if(!loader.LoadScene(mModelUrl, loadParams))
+        {
+          DALI_LOG_ERROR("Failed to load scene from '%s': %s\n", mModelUrl.c_str(), loader.GetParseError().c_str());
+
+          mModelCacheManager.SetSceneLoaded(mModelUrl, false);
+          mModelCacheManager.SetSceneLoading(mModelUrl, false);
+          mModelCacheManager.UnreferenceModelCache(mModelUrl);
+
+          return;
+        }
+      }
+      else if(extension == GLTF_EXTENSION)
+      {
+        Dali::Scene3D::Loader::ShaderDefinitionFactory sdf;
+        sdf.SetResources(mLoadResult.mResources);
+        Dali::Scene3D::Loader::LoadGltfScene(mModelUrl, sdf, mLoadResult);
+      }
+      else
+      {
+        DALI_LOG_ERROR("Unsupported model type.\n");
+
+        mModelCacheManager.SetSceneLoaded(mModelUrl, false);
+        mModelCacheManager.SetSceneLoading(mModelUrl, false);
+        mModelCacheManager.UnreferenceModelCache(mModelUrl);
+
+        return;
+      }
+
+      mModelCacheManager.SetSceneLoaded(mModelUrl, true);
+      mModelCacheManager.SetSceneLoading(mModelUrl, false);
+    }
   }
-  else
+
+  loadSceneConditionalWait.Notify();
+
   {
-    DALI_LOG_ERROR("Unsupported model type.\n");
-    return;
+    ConditionalWait::ScopedLock lock(loadRawResourceConditionalWait);
+
+    while(cacheRefCount > 1 && mLoadResult.mResources.mRawResourcesLoading)
+    {
+      loadRawResourceConditionalWait.Wait();
+    }
   }
 
-  for(auto iRoot : mScene.GetRoots())
   {
-    mResourceRefCounts.push_back(mResources.CreateRefCounter());
-    mScene.CountResourceRefs(iRoot, mResourceChoices, mResourceRefCounts.back());
-    mResources.CountEnvironmentReferences(mResourceRefCounts.back());
+    ConditionalWait::ScopedLock lock(loadRawResourceConditionalWait);
+
+    mResourceRefCount = std::move(mLoadResult.mResources.CreateRefCounter());
 
-    mResources.LoadRawResources(mResourceRefCounts.back(), pathProvider);
+    for(auto iRoot : mLoadResult.mScene.GetRoots())
+    {
+      mLoadResult.mScene.CountResourceRefs(iRoot, mResourceChoices, mResourceRefCount);
+    }
+
+    mLoadResult.mResources.CountEnvironmentReferences(mResourceRefCount);
+
+    mLoadResult.mResources.LoadRawResources(mResourceRefCount, pathProvider);
 
     // glTF Mesh is defined in right hand coordinate system, with positive Y for Up direction.
     // Because DALi uses left hand system, Y direciton will be flipped for environment map sampling.
-    for(auto&& env : mResources.mEnvironmentMaps)
+    for(auto&& env : mLoadResult.mResources.mEnvironmentMaps)
     {
       env.first.mYDirection = Y_DIRECTION;
     }
   }
+
+  loadRawResourceConditionalWait.Notify();
+
   mHasSucceeded = true;
 }
 
index 0e68e06..bfafeb0 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_SCENE3D_MODEL_LOAD_TASK_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
 #include <memory>
 
 // INTERNAL INCLUDES
+#include <dali-scene3d/internal/common/model-cache-manager.h>
 #include <dali-scene3d/public-api/loader/load-result.h>
 #include <dali-scene3d/public-api/loader/scene-definition.h>
+#include <dali/devel-api/threading/conditional-wait.h>
 #include <dali/public-api/adaptor-framework/async-task-manager.h>
 
 namespace Dali
@@ -42,8 +44,8 @@ class ModelLoadTask : public AsyncTask
 public:
   /**
    * Constructor
-   * @param[in] modelUrl model file path.(e.g., glTF, and DLI).
-   * @param[in] resourceDirectoryUrl resource file path that includes binary, image etc.
+   * @param[in] modelUrl Model file path.(e.g., glTF, and DLI).
+   * @param[in] resourceDirectoryUrl Resource file path that includes binary, image etc.
    * @param[in] callback The callback that is called when the operation is completed.
    */
   ModelLoadTask(const std::string& modelUrl, const std::string& resourceDirectoryUrl, CallbackBase* callback);
@@ -81,17 +83,12 @@ public:
   std::string mModelUrl;
   std::string mResourceDirectoryUrl;
 
-  Dali::Scene3D::Loader::ResourceBundle                        mResources;
-  Dali::Scene3D::Loader::SceneDefinition                       mScene;
-  Dali::Scene3D::Loader::SceneMetadata                         mMetaData;
-  std::vector<Dali::Scene3D::Loader::AnimationGroupDefinition> mAnimGroups;
-  std::vector<Dali::Scene3D::Loader::CameraParameters>         mCameraParameters;
-  std::vector<Dali::Scene3D::Loader::LightParameters>          mLights;
-  std::vector<Dali::Scene3D::Loader::AnimationDefinition>      mAnimations;
-
-  Dali::Scene3D::Loader::Customization::Choices         mResourceChoices;
-  std::vector<Dali::Scene3D::Loader::ResourceRefCounts> mResourceRefCounts;
-  bool                                                  mHasSucceeded;
+  Dali::Scene3D::Loader::Customization::Choices mResourceChoices;
+  Dali::Scene3D::Loader::ResourceRefCounts      mResourceRefCount;
+  bool                                          mHasSucceeded;
+
+  ModelCacheManager                 mModelCacheManager;
+  Dali::Scene3D::Loader::LoadResult mLoadResult;
 };
 
 } // namespace Internal
index 31e75e5..b7cb6ef 100644 (file)
@@ -31,6 +31,7 @@
 #include <filesystem>
 
 // INTERNAL INCLUDES
+#include <dali-scene3d/internal/common/model-cache-manager.h>
 #include <dali-scene3d/internal/controls/scene-view/scene-view-impl.h>
 #include <dali-scene3d/public-api/controls/model/model.h>
 #include <dali-scene3d/public-api/loader/animation-definition.h>
@@ -116,8 +117,7 @@ void ConfigureBlendShapeShaders(
   Dali::Scene3D::Loader::ResourceBundle& resources, const Dali::Scene3D::Loader::SceneDefinition& scene, Actor root, std::vector<Dali::Scene3D::Loader::BlendshapeShaderConfigurationRequest>&& requests)
 {
   std::vector<std::string> errors;
-  auto                     onError = [&errors](const std::string& msg)
-  { errors.push_back(msg); };
+  auto                     onError = [&errors](const std::string& msg) { errors.push_back(msg); };
   if(!scene.ConfigureBlendshapeShaders(resources, root, std::move(requests), onError))
   {
     Dali::Scene3D::Loader::ExceptionFlinger flinger(ASSERT_LOCATION);
@@ -193,6 +193,11 @@ Model::Model(const std::string& modelUrl, const std::string& resourceDirectoryUr
 
 Model::~Model()
 {
+  if(ModelCacheManager::Get())
+  {
+    ModelCacheManager::Get().UnreferenceModelCache(mModelUrl);
+  }
+
   ResetResourceTasks();
 }
 
@@ -384,6 +389,45 @@ Dali::Animation Model::GetAnimation(const std::string& name) const
   return animation;
 }
 
+uint32_t Model::GetCameraCount() const
+{
+  return mCameraParameters.size();
+}
+
+Dali::CameraActor Model::GenerateCamera(uint32_t index) const
+{
+  Dali::CameraActor camera;
+  if(mCameraParameters.size() > index)
+  {
+    camera = Dali::CameraActor::New3DCamera();
+    if(!mCameraParameters[index].ConfigureCamera(camera, false))
+    {
+      DALI_LOG_ERROR("Fail to generate %u's camera actor : Some property was not defined. Please check model file.\n", index);
+      camera.Reset();
+      return camera;
+    }
+
+    ApplyCameraTransform(camera);
+  }
+  return camera;
+}
+
+bool Model::ApplyCamera(uint32_t index, Dali::CameraActor camera) const
+{
+  if(camera && mCameraParameters.size() > index)
+  {
+    if(!mCameraParameters[index].ConfigureCamera(camera, false))
+    {
+      DALI_LOG_ERROR("Fail to apply %u's camera actor : Some property was not defined. Please check model file.\n", index);
+      return false;
+    }
+
+    ApplyCameraTransform(camera);
+    return true;
+  }
+  return false;
+}
+
 ///////////////////////////////////////////////////////////
 //
 // Private methods
@@ -399,6 +443,11 @@ void Model::OnSceneConnection(int depth)
 {
   if(!mModelLoadTask && !mModelRoot)
   {
+    if(ModelCacheManager::Get())
+    {
+      ModelCacheManager::Get().ReferenceModelCache(mModelUrl);
+    }
+
     Scene3D::Loader::InitializeGltfLoader();
     mModelLoadTask = new ModelLoadTask(mModelUrl, mResourceDirectoryUrl, MakeCallback(this, &Model::OnModelLoadComplete));
     Dali::AsyncTaskManager::Get().AddTask(mModelLoadTask);
@@ -554,10 +603,29 @@ void Model::UpdateImageBasedLightTexture()
       }
       uint32_t textureCount = textures.GetTextureCount();
       // EnvMap requires at least 2 texture, diffuse and specular
-      if(textureCount > 2u)
+      if(textureCount > 2u &&
+         (textures.GetTexture(textureCount - OFFSET_FOR_DIFFUSE_CUBE_TEXTURE) != currentDiffuseTexture ||
+          textures.GetTexture(textureCount - OFFSET_FOR_SPECULAR_CUBE_TEXTURE) != currentSpecularTexture))
       {
-        textures.SetTexture(textureCount - OFFSET_FOR_DIFFUSE_CUBE_TEXTURE, currentDiffuseTexture);
-        textures.SetTexture(textureCount - OFFSET_FOR_SPECULAR_CUBE_TEXTURE, currentSpecularTexture);
+        Dali::TextureSet newTextures = Dali::TextureSet::New();
+
+        for(uint32_t index = 0u; index < textureCount; ++index)
+        {
+          Dali::Texture texture = textures.GetTexture(index);
+          if(index == textureCount - OFFSET_FOR_DIFFUSE_CUBE_TEXTURE)
+          {
+            texture = currentDiffuseTexture;
+          }
+          else if(index == textureCount - OFFSET_FOR_SPECULAR_CUBE_TEXTURE)
+          {
+            texture = currentSpecularTexture;
+          }
+
+          newTextures.SetTexture(index, texture);
+          newTextures.SetSampler(index, textures.GetSampler(index));
+        }
+
+        renderer.SetTextures(newTextures);
       }
     }
     renderableActor.RegisterProperty(Dali::Scene3D::Loader::NodeDefinition::GetIblScaleFactorUniformName().data(), currentIblScaleFactor);
@@ -583,6 +651,45 @@ void Model::UpdateImageBasedLightScaleFactor()
   }
 }
 
+void Model::ApplyCameraTransform(Dali::CameraActor camera) const
+{
+  Vector3    selfPosition    = Self().GetProperty<Vector3>(Actor::Property::POSITION);
+  Quaternion selfOrientation = Self().GetProperty<Quaternion>(Actor::Property::ORIENTATION);
+  Vector3    selfScale       = Self().GetProperty<Vector3>(Actor::Property::SCALE);
+
+  Vector3    cameraPosition    = camera.GetProperty<Vector3>(Actor::Property::POSITION);
+  Quaternion cameraOrientation = camera.GetProperty<Quaternion>(Actor::Property::ORIENTATION);
+  Vector3    cameraScale       = camera.GetProperty<Vector3>(Actor::Property::SCALE);
+
+  // Models in glTF and dli are defined as right hand coordinate system.
+  // DALi uses left hand coordinate system. Scaling negative is for change winding order.
+  if(!Dali::Equals(Y_DIRECTION.Dot(Vector3::YAXIS), 1.0f))
+  {
+    // Reflect by XZ plane
+    cameraPosition.y = -cameraPosition.y;
+    Quaternion yDirectionQuaternion;
+    yDirectionQuaternion.mVector = Vector3::YAXIS;
+    // Reflect orientation
+    cameraOrientation = yDirectionQuaternion * cameraOrientation * yDirectionQuaternion;
+  }
+
+  Vector3    resultPosition;
+  Quaternion resultOrientation;
+  Vector3    resultScale;
+
+  Matrix selfMatrix(false);
+  Matrix cameraMatrix(false);
+  Matrix resultMatrix(false);
+  selfMatrix.SetTransformComponents(selfScale, selfOrientation, selfPosition);
+  cameraMatrix.SetTransformComponents(cameraScale, cameraOrientation, cameraPosition);
+  Matrix::Multiply(resultMatrix, cameraMatrix, selfMatrix);
+  resultMatrix.GetTransformComponents(resultPosition, resultOrientation, resultScale);
+
+  camera.SetProperty(Actor::Property::POSITION, resultPosition);
+  camera.SetProperty(Actor::Property::ORIENTATION, resultOrientation);
+  camera.SetProperty(Actor::Property::SCALE, resultScale);
+}
+
 void Model::NotifyImageBasedLightTexture(Dali::Texture diffuseTexture, Dali::Texture specularTexture, float scaleFactor)
 {
   if(mSceneDiffuseTexture != diffuseTexture || mSceneSpecularTexture != specularTexture)
@@ -612,6 +719,12 @@ void Model::OnModelLoadComplete()
   if(!mModelLoadTask->HasSucceeded())
   {
     ResetResourceTasks();
+
+    if(ModelCacheManager::Get())
+    {
+      ModelCacheManager::Get().UnreferenceModelCache(mModelUrl);
+    }
+
     return;
   }
 
@@ -619,13 +732,13 @@ void Model::OnModelLoadComplete()
   mRenderableActors.clear();
   CollectRenderableActor(mModelRoot);
 
-  auto* resources = &(mModelLoadTask->mResources);
-  auto* scene     = &(mModelLoadTask->mScene);
-  CreateAnimations(*scene);
-  if(!resources->mEnvironmentMaps.empty())
+  CreateAnimations(mModelLoadTask->mLoadResult.mScene);
+  ResetCameraParameters();
+
+  if(!mModelLoadTask->mLoadResult.mResources.mEnvironmentMaps.empty())
   {
-    mDefaultDiffuseTexture  = resources->mEnvironmentMaps.front().second.mDiffuse;
-    mDefaultSpecularTexture = resources->mEnvironmentMaps.front().second.mSpecular;
+    mDefaultDiffuseTexture  = mModelLoadTask->mLoadResult.mResources.mEnvironmentMaps.front().second.mDiffuse;
+    mDefaultSpecularTexture = mModelLoadTask->mLoadResult.mResources.mEnvironmentMaps.front().second.mSpecular;
   }
 
   UpdateImageBasedLightTexture();
@@ -707,28 +820,26 @@ void Model::CreateModel()
   mModelRoot.SetProperty(Actor::Property::COLOR_MODE, ColorMode::USE_OWN_MULTIPLY_PARENT_COLOR);
 
   BoundingVolume                                      AABB;
-  auto*                                               resources = &(mModelLoadTask->mResources);
-  auto*                                               scene     = &(mModelLoadTask->mScene);
   Dali::Scene3D::Loader::Transforms                   xforms{Dali::Scene3D::Loader::MatrixStack{}, Dali::Scene3D::Loader::ViewProjection{}};
-  Dali::Scene3D::Loader::NodeDefinition::CreateParams nodeParams{*resources, xforms, {}, {}, {}};
-  uint32_t                                            rootCount = 0u;
-  for(auto iRoot : scene->GetRoots())
-  {
-    resources->GenerateResources(mModelLoadTask->mResourceRefCounts[rootCount]);
+  Dali::Scene3D::Loader::NodeDefinition::CreateParams nodeParams{mModelLoadTask->mLoadResult.mResources, xforms, {}, {}, {}};
+
+  // Generate Dali handles from resource bundle. Note that we generate all scene's resouce immediatly.
+  mModelLoadTask->mLoadResult.mResources.GenerateResources(mModelLoadTask->mResourceRefCount);
 
-    if(auto actor = scene->CreateNodes(iRoot, mModelLoadTask->mResourceChoices, nodeParams))
+  for(auto iRoot : mModelLoadTask->mLoadResult.mScene.GetRoots())
+  {
+    if(auto actor = mModelLoadTask->mLoadResult.mScene.CreateNodes(iRoot, mModelLoadTask->mResourceChoices, nodeParams))
     {
-      scene->ConfigureSkeletonJoints(iRoot, resources->mSkeletons, actor);
-      scene->ConfigureSkinningShaders(*resources, actor, std::move(nodeParams.mSkinnables));
-      ConfigureBlendShapeShaders(*resources, *scene, actor, std::move(nodeParams.mBlendshapeRequests));
+      mModelLoadTask->mLoadResult.mScene.ConfigureSkeletonJoints(iRoot, mModelLoadTask->mLoadResult.mResources.mSkeletons, actor);
+      mModelLoadTask->mLoadResult.mScene.ConfigureSkinningShaders(mModelLoadTask->mLoadResult.mResources, actor, std::move(nodeParams.mSkinnables));
+      ConfigureBlendShapeShaders(mModelLoadTask->mLoadResult.mResources, mModelLoadTask->mLoadResult.mScene, actor, std::move(nodeParams.mBlendshapeRequests));
 
-      scene->ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      mModelLoadTask->mLoadResult.mScene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
 
       mModelRoot.Add(actor);
     }
 
-    AddModelTreeToAABB(AABB, *scene, mModelLoadTask->mResourceChoices, iRoot, nodeParams, Matrix::IDENTITY);
-    rootCount++;
+    AddModelTreeToAABB(AABB, mModelLoadTask->mLoadResult.mScene, mModelLoadTask->mResourceChoices, iRoot, nodeParams, Matrix::IDENTITY);
   }
 
   mNaturalSize = AABB.CalculateSize();
@@ -745,10 +856,10 @@ void Model::CreateModel()
 
 void Model::CreateAnimations(Dali::Scene3D::Loader::SceneDefinition& scene)
 {
-  if(!mModelLoadTask->mAnimations.empty())
+  mAnimations.clear();
+  if(!mModelLoadTask->mLoadResult.mAnimationDefinitions.empty())
   {
-    auto getActor = [&](const Scene3D::Loader::AnimatedProperty& property)
-    {
+    auto getActor = [&](const Scene3D::Loader::AnimatedProperty& property) {
       if(property.mNodeIndex == Scene3D::Loader::INVALID_INDEX)
       {
         return mModelRoot.FindChildByName(property.mNodeName);
@@ -761,8 +872,7 @@ void Model::CreateAnimations(Dali::Scene3D::Loader::SceneDefinition& scene)
       return mModelRoot.FindChildById(node->mNodeId);
     };
 
-    mAnimations.clear();
-    for(auto&& animation : mModelLoadTask->mAnimations)
+    for(auto&& animation : mModelLoadTask->mLoadResult.mAnimationDefinitions)
     {
       Dali::Animation anim = animation.ReAnimate(getActor);
       mAnimations.push_back({animation.mName, anim});
@@ -770,6 +880,16 @@ void Model::CreateAnimations(Dali::Scene3D::Loader::SceneDefinition& scene)
   }
 }
 
+void Model::ResetCameraParameters()
+{
+  mCameraParameters.clear();
+  if(!mModelLoadTask->mLoadResult.mCameraParameters.empty())
+  {
+    // Copy camera parameters.
+    std::copy(mModelLoadTask->mLoadResult.mCameraParameters.begin(), mModelLoadTask->mLoadResult.mCameraParameters.end(), std::back_inserter(mCameraParameters));
+  }
+}
+
 } // namespace Internal
 } // namespace Scene3D
 } // namespace Dali
index 36e3a9a..cd1daf5 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_SCENE3D_INTERNAL_MODEL_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
@@ -20,6 +20,7 @@
 
 // EXTERNAL INCLUDES
 #include <dali-toolkit/public-api/controls/control-impl.h>
+#include <dali/public-api/actors/camera-actor.h>
 #include <dali/public-api/actors/layer.h>
 #include <dali/public-api/animation/animation.h>
 #include <dali/public-api/object/weak-handle.h>
@@ -31,6 +32,7 @@
 #include <dali-scene3d/internal/common/model-load-task.h>
 #include <dali-scene3d/public-api/controls/model/model.h>
 #include <dali-scene3d/public-api/controls/scene-view/scene-view.h>
+#include <dali-scene3d/public-api/loader/load-result.h>
 
 namespace Dali
 {
@@ -47,11 +49,10 @@ class Model : public Dali::Toolkit::Internal::Control, public ImageBasedLightObs
 {
 public:
   using AnimationData = std::pair<std::string, Dali::Animation>;
+  using CameraData    = Loader::CameraParameters;
 
   /**
-   * @brief Creates a new Model.
-   *
-   * @return A public handle to the newly allocated Model.
+   * @copydoc Model::New()
    */
   static Dali::Scene3D::Model New(const std::string& modelUrl, const std::string& resourceDirectoryUrl);
 
@@ -115,9 +116,26 @@ public:
    */
   Dali::Animation GetAnimation(const std::string& name) const;
 
+  /**
+   * @copydoc Model::GetCameraCount()
+   */
+  uint32_t GetCameraCount() const;
+
+  /**
+   * @copydoc Model::GenerateCamera()
+   */
+  Dali::CameraActor GenerateCamera(uint32_t index) const;
+
+  /**
+   * @copydoc Model::ApplyCamera()
+   */
+  bool ApplyCamera(uint32_t index, Dali::CameraActor camera) const;
+
 protected:
   /**
    * @brief Constructs a new Model.
+   * @param[in] modelUrl model file path.(e.g., glTF, and DLI).
+   * @param[in] resourceDirectoryUrl resource file path that includes binary, image etc.
    */
   Model(const std::string& modelUrl, const std::string& resourceDirectoryUrl);
 
@@ -167,6 +185,7 @@ private:
    */
   bool IsResourceReady() const override;
 
+private:
   /**
    * @brief Scales the model to fit the control or to return to original size.
    */
@@ -192,6 +211,15 @@ private:
    */
   void UpdateImageBasedLightScaleFactor();
 
+  /**
+   * @brief Apply self transform into inputed camera.
+   * Inputed camera must be configured by CameraParameter. Mean, inputed camera coordinate depend on Model.
+   * After this API finished, CameraActor coordinate system converted as DALi coordinate system.
+   *
+   * @param[in,out] camera CameraActor who need to apply model itself's transform
+   */
+  void ApplyCameraTransform(Dali::CameraActor camera) const;
+
 public: // Overrides ImageBasedLightObserver Methods.
   /**
    * @copydoc Dali::Scene3D::Internal::ImageBasedLightObserver::NotifyImageBasedLightTexture()
@@ -254,11 +282,17 @@ private:
    */
   void CreateAnimations(Dali::Scene3D::Loader::SceneDefinition& scene);
 
+  /**
+   * @brief Reset CameraData from loaded CameraParameters.
+   */
+  void ResetCameraParameters();
+
 private:
   std::string                    mModelUrl;
   std::string                    mResourceDirectoryUrl;
   Dali::Actor                    mModelRoot;
   std::vector<AnimationData>     mAnimations;
+  std::vector<CameraData>        mCameraParameters;
   std::vector<WeakHandle<Actor>> mRenderableActors;
   WeakHandle<Scene3D::SceneView> mParentSceneView;
 
index b5386c3..85b5cbc 100644 (file)
@@ -6,6 +6,7 @@ set(scene3d_src_files ${scene3d_src_files}
        ${scene3d_internal_dir}/algorithm/path-finder-spfa.cpp
        ${scene3d_internal_dir}/algorithm/path-finder-spfa-double-way.cpp
        ${scene3d_internal_dir}/common/environment-map-load-task.cpp
+       ${scene3d_internal_dir}/common/model-cache-manager.cpp
        ${scene3d_internal_dir}/common/model-load-task.cpp
        ${scene3d_internal_dir}/controls/model/model-impl.cpp
        ${scene3d_internal_dir}/controls/scene-view/scene-view-impl.cpp
index 2a1392a..6576803 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef DALI_SCENE3D_LOADER_GLTF2_ASSET_H_
 #define DALI_SCENE3D_LOADER_GLTF2_ASSET_H_
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
  */
 
 // INTERNAL INCLUDES
-#include "dali-scene3d/internal/loader/json-reader.h"
-#include "dali-scene3d/public-api/loader/index.h"
+#include <dali-scene3d/internal/loader/json-reader.h>
+#include <dali-scene3d/public-api/loader/index.h>
 
 // EXTERNAL INCLUDES
+#include <dali/devel-api/common/map-wrapper.h>
+#include <dali/public-api/common/vector-wrapper.h>
+#include <dali/public-api/math/quaternion.h>
+#include <dali/public-api/math/vector4.h>
 #include <cstdint>
 #include <memory>
-#include "dali/devel-api/common/map-wrapper.h"
-#include "dali/public-api/common/vector-wrapper.h"
-#include "dali/public-api/math/quaternion.h"
-#include "dali/public-api/math/vector4.h"
 
 #define ENUM_STRING_MAPPING(t, x) \
   {                               \
@@ -50,7 +50,8 @@
 
 namespace gltf2
 {
-using Index = Dali::Scene3D::Loader::Index;
+using Index                           = Dali::Scene3D::Loader::Index;
+constexpr float UNDEFINED_FLOAT_VALUE = -1.0f; ///< Special marker for some non-negative only float values.
 
 template<typename T>
 class Ref
@@ -350,7 +351,7 @@ struct TextureInfo
  */
 struct MaterialIor
 {
-  float mIor = MAXFLOAT;
+  float mIor = UNDEFINED_FLOAT_VALUE;
 };
 
 /**
@@ -446,20 +447,20 @@ struct Camera : Named
 {
   struct Perspective
   {
-    float mAspectRatio;
-    float mYFov;
-    float mZFar;
-    float mZNear;
+    float mAspectRatio = UNDEFINED_FLOAT_VALUE;
+    float mYFov        = UNDEFINED_FLOAT_VALUE;
+    float mZFar        = UNDEFINED_FLOAT_VALUE;
+    float mZNear       = UNDEFINED_FLOAT_VALUE;
     //TODO: extras
     //TODO: extensions
   };
 
   struct Orthographic
   {
-    float mXMag;
-    float mYMag;
-    float mZFar;
-    float mZNear;
+    float mXMag  = UNDEFINED_FLOAT_VALUE;
+    float mYMag  = UNDEFINED_FLOAT_VALUE;
+    float mZFar  = UNDEFINED_FLOAT_VALUE;
+    float mZNear = UNDEFINED_FLOAT_VALUE;
     //TODO: extras
     //TODO: extensions
   };
index c0369ab..fa792f9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
@@ -122,6 +122,21 @@ Dali::Animation Model::GetAnimation(const std::string& name) const
   return GetImpl(*this).GetAnimation(name);
 }
 
+uint32_t Model::GetCameraCount() const
+{
+  return GetImpl(*this).GetCameraCount();
+}
+
+Dali::CameraActor Model::GenerateCamera(uint32_t index) const
+{
+  return GetImpl(*this).GenerateCamera(index);
+}
+
+bool Model::ApplyCamera(uint32_t index, Dali::CameraActor camera) const
+{
+  return GetImpl(*this).ApplyCamera(index, camera);
+}
+
 } // namespace Scene3D
 
 } // namespace Dali
index 45ceca1..ce723ea 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_SCENE3D_MODEL_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
@@ -23,6 +23,7 @@
 
 // EXTERNAL INCLUDES
 #include <dali-toolkit/public-api/controls/control.h>
+#include <dali/public-api/actors/camera-actor.h>
 #include <dali/public-api/common/dali-common.h>
 #include <dali/public-api/rendering/texture.h>
 
@@ -270,6 +271,43 @@ public:
    */
   Dali::Animation GetAnimation(const std::string& name) const;
 
+  /**
+   * @brief Gets number of camera parameters those loaded from model file.
+   *
+   * @SINCE_2_2.15
+   * @return The number of loaded camera parameters.
+   * @note This method should be called after Model load finished.
+   */
+  uint32_t GetCameraCount() const;
+
+  /**
+   * @brief Generate camera actor using camera parameters at the index.
+   * If camera parameter is valid, create new CameraActor.
+   * Camera parameter decide at initialized time and
+   * didn't apply model node's current position (like Animation).
+   *
+   * @SINCE_2_2.15
+   * @param[in] index Index of camera to be used for generation camera.
+   * @return Generated CameraActor by the index, or empty Handle if generation failed.
+   * @note This method should be called after Model load finished.
+   */
+  Dali::CameraActor GenerateCamera(uint32_t index) const;
+
+  /**
+   * @brief Apply camera parameters at the index to inputed camera actor.
+   * If camera parameter is valid and camera actor is not empty, apply parameters.
+   * It will change camera's transform and near / far / fov or orthographic size / aspect ratio (if defined)
+   * Camera parameter decide at initialized time and
+   * didn't apply model node's current position (like Animation).
+   *
+   * @SINCE_2_2.15
+   * @param[in] index Index of camera to be used for generation camera.
+   * @param[in,out] camera Index of camera to be used for generation camera.
+   * @return True if apply successed. False otherwise.
+   * @note This method should be called after Model load finished.
+   */
+  bool ApplyCamera(uint32_t index, Dali::CameraActor camera) const;
+
 public: // Not intended for application developers
   /// @cond internal
   /**
index 270f84b..cc9a351 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
  * limitations under the License.
  *
  */
-#include "dali-scene3d/public-api/loader/camera-parameters.h"
-#include "dali-scene3d/public-api/loader/utils.h"
-#include "dali/devel-api/actors/camera-actor-devel.h"
-#include "dali/integration-api/debug.h"
-#include "dali/public-api/actors/camera-actor.h"
-#include "dali/public-api/math/quaternion.h"
+
+// CLASS HEADER
+#include <dali-scene3d/public-api/loader/camera-parameters.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/actors/camera-actor-devel.h>
+#include <dali/integration-api/debug.h>
+#include <dali/public-api/actors/camera-actor.h>
+#include <dali/public-api/math/quaternion.h>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/internal/loader/gltf2-asset.h> // for gltf2::UNDEFINED_FLOAT_VALUE
+#include <dali-scene3d/public-api/loader/utils.h>
 
 namespace Dali
 {
@@ -143,11 +150,12 @@ void Orthographic(Matrix& result, float left, float right, float bottom, float t
 ViewProjection CameraParameters::GetViewProjection() const
 {
   ViewProjection viewProjection;
+
   // The projection matrix.
   if(isPerspective)
   {
     Perspective(viewProjection.GetProjection(),
-                Radian(Degree(yFov)),
+                Radian(yFovDegree),
                 1.f,
                 zNear,
                 zFar,
@@ -191,36 +199,75 @@ void CameraParameters::CalculateTransformComponents(Vector3& position, Quaternio
   orientation *= viewQuaternion;
 }
 
-void CameraParameters::ConfigureCamera(CameraActor& camera) const
+bool CameraParameters::ConfigureCamera(CameraActor& camera, bool invertY) const
 {
-  SetActorCentered(camera);
-
   if(isPerspective)
   {
+    if(Dali::Equals(zNear, gltf2::UNDEFINED_FLOAT_VALUE) ||
+       Dali::Equals(yFovDegree.degree, gltf2::UNDEFINED_FLOAT_VALUE))
+    {
+      return false;
+    }
+
     camera.SetProjectionMode(Camera::PERSPECTIVE_PROJECTION);
     camera.SetNearClippingPlane(zNear);
-    camera.SetFarClippingPlane(zFar);
-    camera.SetFieldOfView(Radian(Degree(yFov)));
+    camera.SetFieldOfView(Radian(yFovDegree));
+
+    if(!Dali::Equals(zFar, gltf2::UNDEFINED_FLOAT_VALUE))
+    {
+      camera.SetFarClippingPlane(zFar);
+    }
+    else
+    {
+      // TODO : Infinite perspective projection didn't support yet. Just set big enough value now
+      camera.SetFarClippingPlane(1000.0f);
+    }
+
+    if(!Dali::Equals(aspectRatio, gltf2::UNDEFINED_FLOAT_VALUE))
+    {
+      // TODO: By gltf 2.0 spec, we should not 'crop' and 'non-uniform scaling' by viewport.
+      // If we skip to setup this value, 'non-uniform scaling' problem fixed.
+      // But we need to resolve 'crop' case in future.
+      //camera.SetAspectRatio(aspectRatio);
+    }
   }
   else
   {
+    if(Dali::Equals(zNear, gltf2::UNDEFINED_FLOAT_VALUE) ||
+       Dali::Equals(zFar, gltf2::UNDEFINED_FLOAT_VALUE) ||
+       Dali::Equals(orthographicSize, gltf2::UNDEFINED_FLOAT_VALUE))
+    {
+      return false;
+    }
+
     camera.SetProjectionMode(Camera::ORTHOGRAPHIC_PROJECTION);
     camera.SetNearClippingPlane(zNear);
     camera.SetFarClippingPlane(zFar);
-    camera.SetAspectRatio(aspectRatio);
     camera.SetProperty(Dali::DevelCameraActor::Property::ORTHOGRAPHIC_SIZE, orthographicSize);
+
+    if(!Dali::Equals(aspectRatio, gltf2::UNDEFINED_FLOAT_VALUE))
+    {
+      // TODO: By gltf 2.0 spec, we should not 'crop' and 'non-uniform scaling' by viewport.
+      // If we skip to setup this value, 'non-uniform scaling' problem fixed.
+      // But we need to resolve 'crop' case in future.
+      //camera.SetAspectRatio(aspectRatio);
+    }
   }
 
+  SetActorCentered(camera);
+
   // model
   Vector3    camTranslation;
   Vector3    camScale;
   Quaternion camOrientation;
   CalculateTransformComponents(camTranslation, camOrientation, camScale);
 
-  camera.SetInvertYAxis(true);
+  camera.SetInvertYAxis(invertY);
   camera.SetProperty(Actor::Property::POSITION, camTranslation);
   camera.SetProperty(Actor::Property::ORIENTATION, camOrientation);
   camera.SetProperty(Actor::Property::SCALE, camScale);
+
+  return true;
 }
 
 } // namespace Loader
index 70060e5..9fde071 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef DALI_SCENE3D_LOADER_CAMERA_PARAMETERS_H
 #define DALI_SCENE3D_LOADER_CAMERA_PARAMETERS_H
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
  */
 
 // INTERNAL INCLUDES
-#include "dali-scene3d/public-api/api.h"
-#include "dali-scene3d/public-api/loader/view-projection.h"
+#include <dali-scene3d/public-api/api.h>
+#include <dali-scene3d/public-api/loader/view-projection.h>
 
 // EXTERNAL INCLUDES
-#include "dali/public-api/math/matrix.h"
-#include "dali/public-api/math/vector3.h"
+#include <dali/public-api/math/degree.h>
+#include <dali/public-api/math/matrix.h>
+#include <dali/public-api/math/vector3.h>
 
 namespace Dali
 {
@@ -35,10 +36,11 @@ namespace Loader
 {
 struct DALI_SCENE3D_API CameraParameters
 {
+  // TODO : Is these default value has is meaning?
   Matrix matrix           = Matrix::IDENTITY;
   float  orthographicSize = 1.f;
   float  aspectRatio      = 1.f;
-  float  yFov             = 60.f;
+  Degree yFovDegree       = Degree(60.f);
   float  zNear            = 0.1f;
   float  zFar             = 1000.f;
   bool   isPerspective    = true;
@@ -58,8 +60,10 @@ struct DALI_SCENE3D_API CameraParameters
    * @brief Configures the camera in the way that it is supposed to be used with
    *        scene3d scenes. This means inverted Y and a rotation of 180 degrees
    *        along the Y axis, plus whatever the parameters define.
+   *
+   * @return True if success to generate camera. False otherwise.
    */
-  void ConfigureCamera(CameraActor& camera) const;
+  bool ConfigureCamera(CameraActor& camera, bool invertY = true) const;
 };
 
 } // namespace Loader
index 8953f1a..1a087c9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
@@ -57,12 +57,12 @@ namespace rs = RendererState;
 
 namespace
 {
-const char* NODES         = "nodes";
-const char* SCENES        = "scenes";
-const char* NODE          = "node";
-const char* URI           = "uri";
-const char* URL           = "url";
-const char* HINTS         = "hints";
+const char* NODES  = "nodes";
+const char* SCENES = "scenes";
+const char* NODE   = "node";
+const char* URI    = "uri";
+const char* URL    = "url";
+const char* HINTS  = "hints";
 const char* NAME("name");
 const char* BLEND_SHAPE_HEADER("blendShapeHeader");
 const char* BLEND_SHAPES("blendShapes");
@@ -1729,7 +1729,7 @@ void DliLoader::Impl::GetCameraParameters(std::vector<CameraParameters>& cameras
       {
         auto& jsonCamera = (*i0).second;
 
-        ReadFloat(jsonCamera.GetChild("fov"), iCamera->yFov);
+        ReadFloat(jsonCamera.GetChild("fov"), iCamera->yFovDegree.degree);
         ReadFloat(jsonCamera.GetChild("near"), iCamera->zNear);
         ReadFloat(jsonCamera.GetChild("far"), iCamera->zFar);
         if(ReadVector(jsonCamera.GetChild("orthographic"), dummyFloatArray, 4u))
index 2414b1b..dfd9cc2 100644 (file)
@@ -71,21 +71,29 @@ std::vector<std::vector<float>> ReadBlendShapeKeys(const json_value_s& j)
   return result;\r
 }\r
 \r
-const auto BLEND_SHAPE_READER = std::move(js::Reader<BlendShape>()\r
-                                            .Register(*js::MakeProperty("key", ReadBlendShapeKeys, &BlendShape::mKeys))\r
-                                            .Register(*new js::Property<BlendShape, std::string_view>("name", js::Read::StringView, &BlendShape::mNodeName))\r
-                                            .Register(*js::MakeProperty("morphtarget", js::Read::Number<uint32_t>, &BlendShape::mNumberOfMorphTarget))\r
-                                            .Register(*new js::Property<BlendShape, std::string_view>("blendShapeVersion", js::Read::StringView, &BlendShape::mVersion))\r
-                                            .Register(*new js::Property<BlendShape, std::string_view>("fullName", js::Read::StringView, &BlendShape::mFullName))\r
-                                            .Register(*js::MakeProperty("morphname", js::Read::Array<std::string_view, js::Read::StringView>, &BlendShape::mMorphNames)));\r
-\r
-const auto FACIAL_ANIMATION_READER = std::move(js::Reader<FacialAnimation>()\r
-                                                 .Register(*new js::Property<FacialAnimation, std::string_view>("name", js::Read::StringView, &FacialAnimation::mName))\r
-                                                 .Register(*js::MakeProperty("blendShapes", js::Read::Array<BlendShape, js::ObjectReader<BlendShape>::Read>, &FacialAnimation::mBlendShapes))\r
-                                                 .Register(*new js::Property<FacialAnimation, std::string_view>("version", js::Read::StringView, &FacialAnimation::mVersion))\r
-                                                 .Register(*js::MakeProperty("shapesAmount", js::Read::Number<uint32_t>, &FacialAnimation::mNumberOfShapes))\r
-                                                 .Register(*js::MakeProperty("time", js::Read::Array<uint32_t, js::Read::Number>, &FacialAnimation::mTime))\r
-                                                 .Register(*js::MakeProperty("frames", js::Read::Number<uint32_t>, &FacialAnimation::mNumberOfFrames)));\r
+const js::Reader<BlendShape>& GetBlendShapeReader()\r
+{\r
+  static const auto BLEND_SHAPE_READER = std::move(js::Reader<BlendShape>()\r
+                                                      .Register(*js::MakeProperty("key", ReadBlendShapeKeys, &BlendShape::mKeys))\r
+                                                      .Register(*new js::Property<BlendShape, std::string_view>("name", js::Read::StringView, &BlendShape::mNodeName))\r
+                                                      .Register(*js::MakeProperty("morphtarget", js::Read::Number<uint32_t>, &BlendShape::mNumberOfMorphTarget))\r
+                                                      .Register(*new js::Property<BlendShape, std::string_view>("blendShapeVersion", js::Read::StringView, &BlendShape::mVersion))\r
+                                                      .Register(*new js::Property<BlendShape, std::string_view>("fullName", js::Read::StringView, &BlendShape::mFullName))\r
+                                                      .Register(*js::MakeProperty("morphname", js::Read::Array<std::string_view, js::Read::StringView>, &BlendShape::mMorphNames)));\r
+  return BLEND_SHAPE_READER;\r
+}\r
+\r
+const js::Reader<FacialAnimation>& GetFacialAnimationReader()\r
+{\r
+  static const auto FACIAL_ANIMATION_READER = std::move(js::Reader<FacialAnimation>()\r
+                                                           .Register(*new js::Property<FacialAnimation, std::string_view>("name", js::Read::StringView, &FacialAnimation::mName))\r
+                                                           .Register(*js::MakeProperty("blendShapes", js::Read::Array<BlendShape, js::ObjectReader<BlendShape>::Read>, &FacialAnimation::mBlendShapes))\r
+                                                           .Register(*new js::Property<FacialAnimation, std::string_view>("version", js::Read::StringView, &FacialAnimation::mVersion))\r
+                                                           .Register(*js::MakeProperty("shapesAmount", js::Read::Number<uint32_t>, &FacialAnimation::mNumberOfShapes))\r
+                                                           .Register(*js::MakeProperty("time", js::Read::Array<uint32_t, js::Read::Number>, &FacialAnimation::mTime))\r
+                                                           .Register(*js::MakeProperty("frames", js::Read::Number<uint32_t>, &FacialAnimation::mNumberOfFrames)));\r
+  return FACIAL_ANIMATION_READER;\r
+}\r
 \r
 } // unnamed namespace\r
 \r
@@ -112,14 +120,14 @@ AnimationDefinition LoadFacialAnimation(const std::string& url)
   if(setObjectReaders)\r
   {\r
     // NOTE: only referencing own, anonymous namespace, const objects; the pointers will never need to change.\r
-    js::SetObjectReader(BLEND_SHAPE_READER);\r
+    js::SetObjectReader(GetBlendShapeReader());\r
     setObjectReaders = false;\r
   }\r
 \r
   auto& rootObj = js::Cast<json_object_s>(*root);\r
 \r
   FacialAnimation facialAnimation;\r
-  FACIAL_ANIMATION_READER.Read(rootObj, facialAnimation);\r
+  GetFacialAnimationReader().Read(rootObj, facialAnimation);\r
 \r
   AnimationDefinition animationDefinition;\r
   animationDefinition.mName     = std::string(facialAnimation.mName.data());\r
index 9068b7f..c0ca6cd 100644 (file)
@@ -47,13 +47,13 @@ namespace
 Dali::Mutex gInitializeMutex;
 Dali::Mutex gReadMutex;
 
-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 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,
@@ -100,32 +100,54 @@ void ApplyAccessorMinMax(const gt::Accessor& acc, float* values)
   MeshDefinition::Blob::ApplyMinMax(acc.mMin, acc.mMax, acc.mCount, values);
 }
 
-const auto BUFFER_READER = std::move(js::Reader<gt::Buffer>()
-                                       .Register(*js::MakeProperty("byteLength", js::Read::Number<uint32_t>, &gt::Buffer::mByteLength))
-                                       .Register(*js::MakeProperty("uri", js::Read::StringView, &gt::Buffer::mUri)));
+const 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 auto BUFFER_VIEW_READER = std::move(js::Reader<gt::BufferView>()
-                                            .Register(*js::MakeProperty("buffer", gt::RefReader<gt::Document>::Read<gt::Buffer, &gt::Document::mBuffers>, &gt::BufferView::mBuffer))
-                                            .Register(*js::MakeProperty("byteOffset", js::Read::Number<uint32_t>, &gt::BufferView::mByteOffset))
-                                            .Register(*js::MakeProperty("byteLength", js::Read::Number<uint32_t>, &gt::BufferView::mByteLength))
-                                            .Register(*js::MakeProperty("byteStride", js::Read::Number<uint32_t>, &gt::BufferView::mByteStride))
-                                            .Register(*js::MakeProperty("target", js::Read::Number<uint32_t>, &gt::BufferView::mTarget)));
+const 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 auto BUFFER_VIEW_CLIENT_READER = std::move(js::Reader<gt::BufferViewClient>()
+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 auto COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER = std::move(js::Reader<gt::ComponentTypedBufferViewClient>()
+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 auto ACCESSOR_SPARSE_READER = std::move(js::Reader<gt::Accessor::Sparse>()
+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 auto ACCESSOR_READER = std::move(js::Reader<gt::Accessor>()
+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))
@@ -142,50 +164,86 @@ const auto ACCESSOR_READER = std::move(js::Reader<gt::Accessor>()
                                          .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 auto IMAGE_READER = std::move(js::Reader<gt::Image>()
+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 auto SAMPLER_READER = std::move(js::Reader<gt::Sampler>()
+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 auto TEXURE_READER = std::move(js::Reader<gt::Texture>()
+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 auto TEXURE_INFO_READER = std::move(js::Reader<gt::TextureInfo>()
+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 auto MATERIAL_PBR_READER = std::move(js::Reader<gt::Material::Pbr>()
+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 auto MATERIAL_SPECULAR_READER = std::move(js::Reader<gt::MaterialSpecular>()
+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 auto MATERIAL_IOR_READER = std::move(js::Reader<gt::MaterialIor>()
+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 auto MATERIAL_EXTENSION_READER = std::move(js::Reader<gt::MaterialExtensions>()
+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 auto MATERIAL_READER = std::move(js::Reader<gt::Material>()
+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))
@@ -196,6 +254,8 @@ const auto MATERIAL_READER = std::move(js::Reader<gt::Material>()
                                          .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)
 {
@@ -229,21 +289,31 @@ std::vector<std::map<gt::Attribute::Type, gt::Ref<gt::Accessor>>> ReadMeshPrimit
   return result;
 }
 
-const auto MESH_PRIMITIVE_READER = std::move(js::Reader<gt::Mesh::Primitive>()
+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 auto MESH_READER = std::move(js::Reader<gt::Mesh>()
+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 auto SKIN_READER = std::move(js::Reader<gt::Skin>()
+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>,
@@ -254,26 +324,42 @@ const auto SKIN_READER = std::move(js::Reader<gt::Skin>()
                                      .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 auto CAMERA_PERSPECTIVE_READER = std::move(js::Reader<gt::Camera::Perspective>()
+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 auto CAMERA_ORTHOGRAPHIC_READER = std::move(js::Reader<gt::Camera::Orthographic>()
+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::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 auto CAMERA_READER = std::move(js::Reader<gt::Camera>()
+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 auto NODE_READER = std::move(js::Reader<gt::Node>()
+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))
@@ -283,21 +369,37 @@ const auto NODE_READER = std::move(js::Reader<gt::Node>()
                                      .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 auto ANIMATION_SAMPLER_READER = std::move(js::Reader<gt::Animation::Sampler>()
+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 auto ANIMATION_TARGET_READER = std::move(js::Reader<gt::Animation::Channel::Target>()
+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 auto ANIMATION_CHANNEL_READER = std::move(js::Reader<gt::Animation::Channel>()
+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 auto ANIMATION_READER = std::move(js::Reader<gt::Animation>()
+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>,
@@ -305,14 +407,22 @@ const auto ANIMATION_READER = std::move(js::Reader<gt::Animation>()
                                           .Register(*js::MakeProperty("channels",
                                                                       js::Read::Array<gt::Animation::Channel, js::ObjectReader<gt::Animation::Channel>::Read>,
                                                                       &gt::Animation::mChannels)));
+  return ANIMATION_READER;
+}
 
-const auto SCENE_READER = std::move(js::Reader<gt::Scene>()
+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 auto DOCUMENT_READER = std::move(js::Reader<gt::Document>()
+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))
@@ -353,6 +463,8 @@ const auto DOCUMENT_READER = std::move(js::Reader<gt::Document>()
                                                                      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
 {
@@ -578,7 +690,7 @@ void ConvertMaterial(const gt::Material& material, const std::unordered_map<std:
     matDef.mEmissiveFactor = material.mEmissiveFactor;
   }
 
-  if(material.mMaterialExtensions.mMaterialIor.mIor < MAXFLOAT)
+  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);
@@ -812,18 +924,33 @@ void ConvertCamera(const gt::Camera& camera, CameraParameters& camParams)
   if(camParams.isPerspective)
   {
     auto& perspective = camera.mPerspective;
-    camParams.yFov    = Degree(Radian(perspective.mYFov)).degree;
-    camParams.zNear   = perspective.mZNear;
-    camParams.zFar    = perspective.mZFar;
+    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;
-    camParams.orthographicSize = ortho.mYMag * .5f;
-    camParams.aspectRatio      = ortho.mXMag / ortho.mYMag;
-    camParams.zNear            = ortho.mZNear;
-    camParams.zFar             = ortho.mZFar;
+    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;
   }
 }
 
@@ -1258,33 +1385,33 @@ void ProduceShaders(ShaderDefinitionFactory& shaderFactory, SceneDefinition& sce
 
 void SetObjectReaders()
 {
-  js::SetObjectReader(BUFFER_READER);
-  js::SetObjectReader(BUFFER_VIEW_READER);
-  js::SetObjectReader(BUFFER_VIEW_CLIENT_READER);
-  js::SetObjectReader(COMPONENT_TYPED_BUFFER_VIEW_CLIENT_READER);
-  js::SetObjectReader(ACCESSOR_SPARSE_READER);
-  js::SetObjectReader(ACCESSOR_READER);
-  js::SetObjectReader(IMAGE_READER);
-  js::SetObjectReader(SAMPLER_READER);
-  js::SetObjectReader(TEXURE_READER);
-  js::SetObjectReader(TEXURE_INFO_READER);
-  js::SetObjectReader(MATERIAL_PBR_READER);
-  js::SetObjectReader(MATERIAL_SPECULAR_READER);
-  js::SetObjectReader(MATERIAL_IOR_READER);
-  js::SetObjectReader(MATERIAL_EXTENSION_READER);
-  js::SetObjectReader(MATERIAL_READER);
-  js::SetObjectReader(MESH_PRIMITIVE_READER);
-  js::SetObjectReader(MESH_READER);
-  js::SetObjectReader(SKIN_READER);
-  js::SetObjectReader(CAMERA_PERSPECTIVE_READER);
-  js::SetObjectReader(CAMERA_ORTHOGRAPHIC_READER);
-  js::SetObjectReader(CAMERA_READER);
-  js::SetObjectReader(NODE_READER);
-  js::SetObjectReader(ANIMATION_SAMPLER_READER);
-  js::SetObjectReader(ANIMATION_TARGET_READER);
-  js::SetObjectReader(ANIMATION_CHANNEL_READER);
-  js::SetObjectReader(ANIMATION_READER);
-  js::SetObjectReader(SCENE_READER);
+  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)
@@ -1349,7 +1476,7 @@ void LoadGltfScene(const std::string& url, ShaderDefinitionFactory& shaderFactor
   {
     Mutex::ScopedLock lock(gReadMutex);
     gt::SetRefReaderObject(doc);
-    DOCUMENT_READER.Read(rootObj, doc);
+    GetDocumentReader().Read(rootObj, doc);
   }
 
   auto              path = url.substr(0, url.rfind('/') + 1);
index 76c1056..826e713 100644 (file)
@@ -54,32 +54,43 @@ struct ImageData
   ImageData::SamplingMode::Type mSamplingMode{ImageData::SamplingMode::BOX_THEN_LINEAR}; ///< The sampling mode used to resize the image.\r
 };\r
 \r
-const std::map<std::string_view, ImageData::SamplingMode::Type> SAMPLING_MODE_TYPES{\r
-  ENUM_STRING_MAPPING(ImageData::SamplingMode, BOX),\r
-  ENUM_STRING_MAPPING(ImageData::SamplingMode, NEAREST),\r
-  ENUM_STRING_MAPPING(ImageData::SamplingMode, LINEAR),\r
-  ENUM_STRING_MAPPING(ImageData::SamplingMode, BOX_THEN_NEAREST),\r
-  ENUM_STRING_MAPPING(ImageData::SamplingMode, BOX_THEN_LINEAR),\r
-  ENUM_STRING_MAPPING(ImageData::SamplingMode, NO_FILTER),\r
-  ENUM_STRING_MAPPING(ImageData::SamplingMode, DONT_CARE),\r
-};\r
+const std::map<std::string_view, ImageData::SamplingMode::Type>& GetStringSamplingModeTable()\r
+{\r
+  static const std::map<std::string_view, ImageData::SamplingMode::Type> SAMPLING_MODE_TYPES{\r
+    ENUM_STRING_MAPPING(ImageData::SamplingMode, BOX),\r
+    ENUM_STRING_MAPPING(ImageData::SamplingMode, NEAREST),\r
+    ENUM_STRING_MAPPING(ImageData::SamplingMode, LINEAR),\r
+    ENUM_STRING_MAPPING(ImageData::SamplingMode, BOX_THEN_NEAREST),\r
+    ENUM_STRING_MAPPING(ImageData::SamplingMode, BOX_THEN_LINEAR),\r
+    ENUM_STRING_MAPPING(ImageData::SamplingMode, NO_FILTER),\r
+    ENUM_STRING_MAPPING(ImageData::SamplingMode, DONT_CARE),\r
+  };\r
+  return SAMPLING_MODE_TYPES;\r
+}\r
 \r
-ENUM_TYPE_FROM_STRING(ImageData::SamplingMode, SAMPLING_MODE_TYPES)\r
+ENUM_TYPE_FROM_STRING(ImageData::SamplingMode, GetStringSamplingModeTable())\r
 \r
 struct MetaData\r
 {\r
   std::vector<ImageData> mImageData;\r
 };\r
 \r
-const auto IMAGE_METADATA_READER = std::move(js::Reader<ImageData>()\r
-                                               .Register(*js::MakeProperty("uri", js::Read::String, &ImageData::mImageUri))\r
-                                               .Register(*js::MakeProperty("minWidth", js::Read::Number, &ImageData::mMinWidth))\r
-                                               .Register(*js::MakeProperty("minHeight", js::Read::Number, &ImageData::mMinHeight))\r
-                                               .Register(*js::MakeProperty("samplingMode", gt::ReadStringEnum<ImageData::SamplingMode>, &ImageData::mSamplingMode)));\r
-\r
-const auto METADATA_READER = std::move(js::Reader<MetaData>()\r
-                                         .Register(*js::MakeProperty("images", js::Read::Array<ImageData, js::ObjectReader<ImageData>::Read>, &MetaData::mImageData)));\r
+const js::Reader<ImageData>& GetImageMetaDataReader()\r
+{\r
+  static const auto IMAGE_METADATA_READER = std::move(js::Reader<ImageData>()\r
+                                                         .Register(*js::MakeProperty("uri", js::Read::String, &ImageData::mImageUri))\r
+                                                         .Register(*js::MakeProperty("minWidth", js::Read::Number, &ImageData::mMinWidth))\r
+                                                         .Register(*js::MakeProperty("minHeight", js::Read::Number, &ImageData::mMinHeight))\r
+                                                         .Register(*js::MakeProperty("samplingMode", gt::ReadStringEnum<ImageData::SamplingMode>, &ImageData::mSamplingMode)));\r
+  return IMAGE_METADATA_READER;\r
+}\r
 \r
+const js::Reader<MetaData>& GetMetaDataReader()\r
+{\r
+  static const auto METADATA_READER = std::move(js::Reader<MetaData>()\r
+                                                   .Register(*js::MakeProperty("images", js::Read::Array<ImageData, js::ObjectReader<ImageData>::Read>, &MetaData::mImageData)));\r
+  return METADATA_READER;\r
+}\r
 } // namespace\r
 \r
 void LoadSceneMetadata(const std::string& url, SceneMetadata& sceneMetadata)\r
@@ -101,13 +112,13 @@ void LoadSceneMetadata(const std::string& url, SceneMetadata& sceneMetadata)
   static bool setObjectReaders = true;\r
   if(setObjectReaders)\r
   {\r
-    js::SetObjectReader(IMAGE_METADATA_READER);\r
+    js::SetObjectReader(GetImageMetaDataReader());\r
 \r
     setObjectReaders = false;\r
   }\r
 \r
   MetaData metaData;\r
-  METADATA_READER.Read(rootObj, metaData);\r
+  GetMetaDataReader().Read(rootObj, metaData);\r
 \r
   sceneMetadata.mImageMetadata.reserve(metaData.mImageData.size() + metaData.mImageData.size());\r
   for(auto&& data : metaData.mImageData)\r
index b23140f..18093a5 100644 (file)
@@ -35,36 +35,39 @@ namespace Loader
 {
 namespace
 {
+template<bool use32BitIndices>
 class IndexProvider
 {
 public:
+  using IndexType = typename std::conditional_t<use32BitIndices, uint32_t, uint16_t>;
   IndexProvider(const uint16_t* indices)
   : mData(reinterpret_cast<uintptr_t>(indices)),
     mFunc(indices ? IncrementPointer : Increment)
   {
   }
 
-  uint16_t operator()()
+  IndexType operator()()
   {
     return mFunc(mData);
   }
 
 private:
-  static uint16_t Increment(uintptr_t& data)
+  static IndexType Increment(uintptr_t& data)
   {
-    return static_cast<uint16_t>(data++);
+    // mData was 'zero' at construct time. Just simply return counter start with 0.
+    return static_cast<IndexType>(data++);
   }
 
-  static uint16_t IncrementPointer(uintptr_t& data)
+  static IndexType IncrementPointer(uintptr_t& data)
   {
-    auto iPtr   = reinterpret_cast<const uint16_t*>(data);
+    auto iPtr   = reinterpret_cast<const IndexType*>(data);
     auto result = *iPtr;
     data        = reinterpret_cast<uintptr_t>(++iPtr);
     return result;
   }
 
   uintptr_t mData;
-  uint16_t (*mFunc)(uintptr_t&);
+  IndexType (*mFunc)(uintptr_t&);
 };
 
 const char* QUAD("quad");
@@ -212,13 +215,23 @@ void ReadJointAccessor(MeshDefinition::RawData& raw, const MeshDefinition::Acces
   raw.mAttribs.push_back({"aJoints", Property::VECTOR4, static_cast<uint32_t>(outBufferSize / sizeof(Vector4)), std::move(buffer)});
 }
 
-void GenerateNormals(MeshDefinition::RawData& raw)
+template<bool use32BitsIndices, typename IndexProviderType = IndexProvider<use32BitsIndices>>
+bool GenerateNormals(MeshDefinition::RawData& raw)
 {
+  using IndexType = typename IndexProviderType::IndexType;
+
+  // mIndicies size must be even if we use 32bit indices.
+  if(DALI_UNLIKELY(use32BitsIndices && !raw.mIndices.empty() && !(raw.mIndices.size() % (sizeof(IndexType) / sizeof(uint16_t)) == 0)))
+  {
+    return false;
+  }
+
   auto& attribs = raw.mAttribs;
   DALI_ASSERT_DEBUG(attribs.size() > 0); // positions
-  IndexProvider getIndex(raw.mIndices.data());
 
-  const uint32_t numIndices = raw.mIndices.empty() ? attribs[0].mNumElements : static_cast<uint32_t>(raw.mIndices.size());
+  IndexProviderType getIndex(raw.mIndices.data());
+
+  const uint32_t numIndices = raw.mIndices.empty() ? attribs[0].mNumElements : static_cast<uint32_t>(raw.mIndices.size() / (sizeof(IndexType) / sizeof(uint16_t)));
 
   auto* positions = reinterpret_cast<const Vector3*>(attribs[0].mData.data());
 
@@ -227,8 +240,8 @@ void GenerateNormals(MeshDefinition::RawData& raw)
 
   for(uint32_t i = 0; i < numIndices; i += 3)
   {
-    uint16_t indices[]{getIndex(), getIndex(), getIndex()};
-    Vector3  pos[]{positions[indices[0]], positions[indices[1]], positions[indices[2]]};
+    IndexType indices[]{getIndex(), getIndex(), getIndex()};
+    Vector3   pos[]{positions[indices[0]], positions[indices[1]], positions[indices[2]]};
 
     Vector3 a = pos[1] - pos[0];
     Vector3 b = pos[2] - pos[0];
@@ -247,14 +260,24 @@ void GenerateNormals(MeshDefinition::RawData& raw)
   }
 
   attribs.push_back({"aNormal", Property::VECTOR3, attribs[0].mNumElements, std::move(buffer)});
+
+  return true;
 }
 
-template<bool useVec3, bool hasUvs, typename T = std::conditional_t<useVec3, Vector3, Vector4>, typename = std::enable_if_t<(std::is_same<T, Vector3>::value || std::is_same<T, Vector4>::value)>>
+template<bool use32BitsIndices, bool useVec3, bool hasUvs, typename T = std::conditional_t<useVec3, Vector3, Vector4>, typename = std::enable_if_t<(std::is_same<T, Vector3>::value || std::is_same<T, Vector4>::value)>, typename IndexProviderType = IndexProvider<use32BitsIndices>>
 bool GenerateTangents(MeshDefinition::RawData& raw)
 {
+  using IndexType = typename IndexProviderType::IndexType;
+
+  // mIndicies size must be even if we use 32bit indices.
+  if(DALI_UNLIKELY(use32BitsIndices && !raw.mIndices.empty() && !(raw.mIndices.size() % (sizeof(IndexType) / sizeof(uint16_t)) == 0)))
+  {
+    return false;
+  }
+
   auto& attribs = raw.mAttribs;
   // Required positions, normals, uvs (if we have). If not, skip generation
-  if(attribs.size() < (2 + static_cast<size_t>(hasUvs)))
+  if(DALI_UNLIKELY(attribs.size() < (2 + static_cast<size_t>(hasUvs))))
   {
     return false;
   }
@@ -264,17 +287,18 @@ bool GenerateTangents(MeshDefinition::RawData& raw)
 
   if constexpr(hasUvs)
   {
-    IndexProvider  getIndex(raw.mIndices.data());
-    const uint32_t numIndices = raw.mIndices.empty() ? attribs[0].mNumElements : static_cast<uint32_t>(raw.mIndices.size());
+    IndexProviderType getIndex(raw.mIndices.data());
+
+    const uint32_t numIndices = raw.mIndices.empty() ? attribs[0].mNumElements : static_cast<uint32_t>(raw.mIndices.size() / (sizeof(IndexType) / sizeof(uint16_t)));
 
     auto* positions = reinterpret_cast<const Vector3*>(attribs[0].mData.data());
     auto* uvs       = reinterpret_cast<const Vector2*>(attribs[2].mData.data());
 
     for(uint32_t i = 0; i < numIndices; i += 3)
     {
-      uint16_t indices[]{getIndex(), getIndex(), getIndex()};
-      Vector3  pos[]{positions[indices[0]], positions[indices[1]], positions[indices[2]]};
-      Vector2  uv[]{uvs[indices[0]], uvs[indices[1]], uvs[indices[2]]};
+      IndexType indices[]{getIndex(), getIndex(), getIndex()};
+      Vector3   pos[]{positions[indices[0]], positions[indices[1]], positions[indices[2]]};
+      Vector2   uv[]{uvs[indices[0]], uvs[indices[1]], uvs[indices[2]]};
 
       float x0 = pos[1].x - pos[0].x;
       float y0 = pos[1].y - pos[0].y;
@@ -670,18 +694,6 @@ MeshDefinition::LoadRaw(const std::string& modelsPath, BufferDefinition::Vector&
       {
         ExceptionFlinger(ASSERT_LOCATION) << "Failed to read indices from '" << path << "'.";
       }
-
-      auto u16s = raw.mIndices.data();
-      auto u32s = reinterpret_cast<uint32_t*>(raw.mIndices.data());
-      auto end  = u32s + indexCount;
-      while(u32s != end)
-      {
-        *u16s = static_cast<uint16_t>(*u32s);
-        ++u16s;
-        ++u32s;
-      }
-
-      raw.mIndices.resize(indexCount);
     }
     else if(MaskMatch(mFlags, U8_INDICES))
     {
@@ -689,7 +701,7 @@ MeshDefinition::LoadRaw(const std::string& modelsPath, BufferDefinition::Vector&
                           mIndices.mBlob.mStride >= sizeof(uint8_t)) &&
                          "Index buffer length not a multiple of element size");
       const auto indexCount = mIndices.mBlob.GetBufferSize() / sizeof(uint8_t);
-      raw.mIndices.resize(indexCount); // NOTE: we need space for uint32_ts initially.
+      raw.mIndices.resize(indexCount); // NOTE: we need space for uint16_ts initially.
 
       std::string path;
       auto        u8s    = reinterpret_cast<uint8_t*>(raw.mIndices.data()) + indexCount;
@@ -783,8 +795,20 @@ MeshDefinition::LoadRaw(const std::string& modelsPath, BufferDefinition::Vector&
   else if(mNormals.mBlob.mLength != 0 && isTriangles)
   {
     DALI_ASSERT_DEBUG(mNormals.mBlob.mLength == mPositions.mBlob.GetBufferSize());
-    GenerateNormals(raw);
-    hasNormals = true;
+    static const std::function<bool(RawData&)> GenerateNormalsFunction[2] =
+      {
+        GenerateNormals<false>,
+        GenerateNormals<true>,
+      };
+    const bool generateSuccessed = GenerateNormalsFunction[MaskMatch(mFlags, U32_INDICES)](raw);
+    if(!generateSuccessed)
+    {
+      DALI_LOG_ERROR("Failed to generate normal\n");
+    }
+    else
+    {
+      hasNormals = true;
+    }
   }
 
   const auto hasUvs = mTexCoords.IsDefined();
@@ -815,7 +839,7 @@ MeshDefinition::LoadRaw(const std::string& modelsPath, BufferDefinition::Vector&
       }
     }
 
-    mTexCoords.mBlob.ApplyMinMax(static_cast<uint32_t>(bufferSize / sizeof(Vector2)), reinterpret_cast<float*>(buffer.data()));
+    mTexCoords.mBlob.ApplyMinMax(static_cast<uint32_t>(uvCount), reinterpret_cast<float*>(buffer.data()));
 
     raw.mAttribs.push_back({"aTexCoord", Property::VECTOR2, static_cast<uint32_t>(uvCount), std::move(buffer)});
   }
@@ -842,18 +866,29 @@ MeshDefinition::LoadRaw(const std::string& modelsPath, BufferDefinition::Vector&
   else if(mTangents.mBlob.mLength != 0 && hasNormals && isTriangles)
   {
     DALI_ASSERT_DEBUG(mTangents.mBlob.mLength == mNormals.mBlob.GetBufferSize());
-    static const std::function<bool(RawData&)> GenerateTangentsFunction[2][2] =
+    static const std::function<bool(RawData&)> GenerateTangentsFunction[2][2][2] =
       {
         {
-          GenerateTangents<false, false>,
-          GenerateTangents<false, true>,
+          {
+            GenerateTangents<false, false, false>,
+            GenerateTangents<false, false, true>,
+          },
+          {
+            GenerateTangents<false, true, false>,
+            GenerateTangents<false, true, true>,
+          },
         },
         {
-          GenerateTangents<true, false>,
-          GenerateTangents<true, true>,
-        },
-      };
-    const bool generateSuccessed = GenerateTangentsFunction[mTangentType == Property::VECTOR3][hasUvs](raw);
+          {
+            GenerateTangents<true, false, false>,
+            GenerateTangents<true, false, true>,
+          },
+          {
+            GenerateTangents<true, true, false>,
+            GenerateTangents<true, true, true>,
+          },
+        }};
+    const bool generateSuccessed = GenerateTangentsFunction[MaskMatch(mFlags, U32_INDICES)][mTangentType == Property::VECTOR3][hasUvs](raw);
     if(!generateSuccessed)
     {
       DALI_LOG_ERROR("Failed to generate tangents\n");
@@ -1008,7 +1043,15 @@ MeshGeometry MeshDefinition::Load(RawData&& raw) const
   {
     if(!raw.mIndices.empty())
     {
-      meshGeometry.geometry.SetIndexBuffer(raw.mIndices.data(), raw.mIndices.size());
+      if(MaskMatch(mFlags, U32_INDICES))
+      {
+        // TODO : We can only store indeces as uint16_type. Send Dali::Geometry that we use it as uint32_t actual.
+        meshGeometry.geometry.SetIndexBuffer(reinterpret_cast<const uint32_t*>(raw.mIndices.data()), raw.mIndices.size() / 2);
+      }
+      else
+      {
+        meshGeometry.geometry.SetIndexBuffer(raw.mIndices.data(), raw.mIndices.size());
+      }
     }
 
     for(auto& a : raw.mAttribs)
index ee2e898..98c8de6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
  */
 
 // FILE HEADER
-#include "dali-scene3d/public-api/loader/resource-bundle.h"
+#include <dali-scene3d/public-api/loader/resource-bundle.h>
 
 // EXTERNAL
+#include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
+#include <dali/public-api/rendering/sampler.h>
 #include <cstring>
 #include <fstream>
 #include <istream>
-#include "dali-toolkit/public-api/image-loader/sync-image-loader.h"
-#include "dali/public-api/rendering/sampler.h"
 
 namespace Dali
 {
@@ -49,6 +49,14 @@ const char* GetResourceTypeName(ResourceType::Value type)
   return RESOURCE_TYPE_NAMES[static_cast<int>(type)];
 }
 
+ResourceBundle::ResourceBundle()
+: mRawResourcesLoading(false),
+  mResourcesGenerating(false),
+  mRawResourcesLoaded(false),
+  mResourcesGenerated(false)
+{
+};
+
 ResourceRefCounts ResourceBundle::CreateRefCounter() const
 {
   ResourceRefCounts refCounts(4);
@@ -75,6 +83,9 @@ void ResourceBundle::CountEnvironmentReferences(ResourceRefCounts& refCounts) co
 
 void ResourceBundle::LoadResources(const ResourceRefCounts& refCounts, PathProvider pathProvider, Options::Type options)
 {
+  mRawResourcesLoading = true;
+  mResourcesGenerating = true;
+
   const auto kForceLoad  = MaskMatch(options, Options::ForceReload);
   const auto kKeepUnused = MaskMatch(options, Options::KeepUnused);
 
@@ -146,117 +157,186 @@ void ResourceBundle::LoadResources(const ResourceRefCounts& refCounts, PathProvi
       iMaterial.second = TextureSet();
     }
   }
+
+  mRawResourcesLoading = false;
+  mResourcesGenerating = false;
+
+  mRawResourcesLoaded = true;
+  mResourcesGenerated = true;
 }
 
 void ResourceBundle::LoadRawResources(const ResourceRefCounts& refCounts, PathProvider pathProvider, Options::Type options)
 {
-  const auto kForceLoad  = MaskMatch(options, Options::ForceReload);
+  const auto kForceLoad = MaskMatch(options, Options::ForceReload);
 
-  const auto& refCountEnvMaps  = refCounts[ResourceType::Environment];
-  auto        environmentsPath = pathProvider(ResourceType::Environment);
-  for(uint32_t i = 0, iEnd = refCountEnvMaps.Size(); i != iEnd; ++i)
+  if(kForceLoad || (!mRawResourcesLoaded && !mRawResourcesLoading))
   {
-    auto  refCount = refCountEnvMaps[i];
-    auto& iEnvMap  = mEnvironmentMaps[i];
-    if(refCount > 0 && (kForceLoad || !iEnvMap.second.IsLoaded()))
+    mRawResourcesLoading = true;
+
+    const auto& refCountEnvMaps  = refCounts[ResourceType::Environment];
+    auto        environmentsPath = pathProvider(ResourceType::Environment);
+    for(uint32_t i = 0, iEnd = refCountEnvMaps.Size(); i != iEnd; ++i)
     {
-      iEnvMap.first.mRawData = std::make_shared<EnvironmentDefinition::RawData>(iEnvMap.first.LoadRaw(environmentsPath));
+      auto  refCount = refCountEnvMaps[i];
+      auto& iEnvMap  = mEnvironmentMaps[i];
+      if(refCount > 0 && (kForceLoad || (!iEnvMap.first.mRawData && !iEnvMap.second.IsLoaded())))
+      {
+        iEnvMap.first.mRawData = std::make_shared<EnvironmentDefinition::RawData>(iEnvMap.first.LoadRaw(environmentsPath));
+      }
     }
-  }
 
-  const auto& refCountShaders = refCounts[ResourceType::Shader];
-  auto        shadersPath     = pathProvider(ResourceType::Shader);
-  for(uint32_t i = 0, iEnd = refCountShaders.Size(); i != iEnd; ++i)
-  {
-    auto  refCount = refCountShaders[i];
-    auto& iShader  = mShaders[i];
-    if(refCount > 0 && (kForceLoad || !iShader.second))
+    const auto& refCountShaders = refCounts[ResourceType::Shader];
+    auto        shadersPath     = pathProvider(ResourceType::Shader);
+    for(uint32_t i = 0, iEnd = refCountShaders.Size(); i != iEnd; ++i)
     {
-      iShader.first.mRawData = std::make_shared<ShaderDefinition::RawData>(iShader.first.LoadRaw(shadersPath));
+      auto  refCount = refCountShaders[i];
+      auto& iShader  = mShaders[i];
+      if(refCount > 0 && (kForceLoad || !iShader.second))
+      {
+        iShader.first.mRawData = std::make_shared<ShaderDefinition::RawData>(iShader.first.LoadRaw(shadersPath));
+      }
     }
-  }
 
-  const auto& refCountMeshes = refCounts[ResourceType::Mesh];
-  auto        modelsPath     = pathProvider(ResourceType::Mesh);
-  for(uint32_t i = 0, iEnd = refCountMeshes.Size(); i != iEnd; ++i)
-  {
-    auto  refCount = refCountMeshes[i];
-    auto& iMesh    = mMeshes[i];
-    if(refCount > 0 && (kForceLoad || !iMesh.second.geometry))
+    const auto& refCountMeshes = refCounts[ResourceType::Mesh];
+    auto        modelsPath     = pathProvider(ResourceType::Mesh);
+    for(uint32_t i = 0, iEnd = refCountMeshes.Size(); i != iEnd; ++i)
     {
-      iMesh.first.mRawData = std::make_shared<MeshDefinition::RawData>(iMesh.first.LoadRaw(modelsPath, mBuffers));
+      auto  refCount = refCountMeshes[i];
+      auto& iMesh    = mMeshes[i];
+      if(refCount > 0 && (kForceLoad || (!iMesh.first.mRawData && !iMesh.second.geometry)))
+      {
+        iMesh.first.mRawData = std::make_shared<MeshDefinition::RawData>(iMesh.first.LoadRaw(modelsPath, mBuffers));
+      }
     }
-  }
 
-  const auto& refCountMaterials = refCounts[ResourceType::Material];
-  auto        imagesPath        = pathProvider(ResourceType::Material);
-  for(uint32_t i = 0, iEnd = refCountMaterials.Size(); i != iEnd; ++i)
-  {
-    auto  refCount  = refCountMaterials[i];
-    auto& iMaterial = mMaterials[i];
-    if(refCount > 0 && (kForceLoad || !iMaterial.second))
+    const auto& refCountMaterials = refCounts[ResourceType::Material];
+    auto        imagesPath        = pathProvider(ResourceType::Material);
+    for(uint32_t i = 0, iEnd = refCountMaterials.Size(); i != iEnd; ++i)
     {
-      iMaterial.first.mRawData = std::make_shared<MaterialDefinition::RawData>(iMaterial.first.LoadRaw(imagesPath));
+      auto  refCount  = refCountMaterials[i];
+      auto& iMaterial = mMaterials[i];
+      if(refCount > 0 && (kForceLoad || (!iMaterial.first.mRawData && !iMaterial.second)))
+      {
+        iMaterial.first.mRawData = std::make_shared<MaterialDefinition::RawData>(iMaterial.first.LoadRaw(imagesPath));
+      }
     }
+
+    mRawResourcesLoading = false;
+    mRawResourcesLoaded  = true;
   }
 }
 
 void ResourceBundle::GenerateResources(const ResourceRefCounts& refCounts, Options::Type options)
 {
-  const auto& refCountEnvMaps = refCounts[ResourceType::Environment];
-  for(uint32_t i = 0, iEnd = refCountEnvMaps.Size(); i != iEnd; ++i)
-  {
-    auto& iEnvMap = mEnvironmentMaps[i];
-    if(iEnvMap.first.mRawData)
-    {
-      iEnvMap.second = iEnvMap.first.Load(std::move(*(iEnvMap.first.mRawData)));
-    }
-    else
-    {
-      iEnvMap.second.mDiffuse  = Texture();
-      iEnvMap.second.mSpecular = Texture();
-    }
-  }
+  const auto kForceLoad = MaskMatch(options, Options::ForceReload);
 
-  const auto& refCountShaders = refCounts[ResourceType::Shader];
-  for(uint32_t i = 0, iEnd = refCountShaders.Size(); i != iEnd; ++i)
+  if(mRawResourcesLoaded)
   {
-    auto& iShader = mShaders[i];
-    if(iShader.first.mRawData)
+    if(kForceLoad || (!mResourcesGenerated && !mResourcesGenerating))
     {
-      iShader.second = iShader.first.Load(std::move(*(iShader.first.mRawData)));
-    }
-    else
-    {
-      iShader.second = Shader();
-    }
-  }
+      mResourcesGenerating = true;
 
-  const auto& refCountMeshes = refCounts[ResourceType::Mesh];
-  for(uint32_t i = 0, iEnd = refCountMeshes.Size(); i != iEnd; ++i)
-  {
-    auto& iMesh = mMeshes[i];
-    if(iMesh.first.mRawData)
-    {
-      iMesh.second = iMesh.first.Load(std::move(*(iMesh.first.mRawData)));
-    }
-    else
-    {
-      iMesh.second.geometry = Geometry();
-    }
-  }
+      const auto& refCountEnvMaps = refCounts[ResourceType::Environment];
+      for(uint32_t i = 0, iEnd = refCountEnvMaps.Size(); i != iEnd; ++i)
+      {
+        auto  refCount = refCountEnvMaps[i];
+        auto& iEnvMap  = mEnvironmentMaps[i];
+        if(refCount > 0 && (kForceLoad || !iEnvMap.second.IsLoaded()))
+        {
+          if(iEnvMap.first.mRawData)
+          {
+            iEnvMap.second = iEnvMap.first.Load(std::move(*(iEnvMap.first.mRawData)));
+          }
+          else
+          {
+            iEnvMap.second.mDiffuse  = Texture();
+            iEnvMap.second.mSpecular = Texture();
+          }
+        }
+      }
 
-  const auto& refCountMaterials = refCounts[ResourceType::Material];
-  for(uint32_t i = 0, iEnd = refCountMaterials.Size(); i != iEnd; ++i)
-  {
-    auto& iMaterial = mMaterials[i];
-    if(iMaterial.first.mRawData)
-    {
-      iMaterial.second = iMaterial.first.Load(mEnvironmentMaps, std::move(*(iMaterial.first.mRawData)));
+      const auto& refCountShaders = refCounts[ResourceType::Shader];
+      for(uint32_t i = 0, iEnd = refCountShaders.Size(); i != iEnd; ++i)
+      {
+        auto  refCount = refCountShaders[i];
+        auto& iShader  = mShaders[i];
+        if(refCount > 0 && (kForceLoad || !iShader.second))
+        {
+          if(iShader.first.mRawData)
+          {
+            iShader.second = iShader.first.Load(std::move(*(iShader.first.mRawData)));
+          }
+          else
+          {
+            iShader.second = Shader();
+          }
+        }
+      }
+
+      const auto& refCountMeshes = refCounts[ResourceType::Mesh];
+      for(uint32_t i = 0, iEnd = refCountMeshes.Size(); i != iEnd; ++i)
+      {
+        auto  refCount = refCountMeshes[i];
+        auto& iMesh    = mMeshes[i];
+        if(refCount > 0 && (kForceLoad || !iMesh.second.geometry))
+        {
+          if(iMesh.first.mRawData)
+          {
+            iMesh.second = iMesh.first.Load(std::move(*(iMesh.first.mRawData)));
+          }
+          else
+          {
+            iMesh.second.geometry = Geometry();
+          }
+        }
+      }
+
+      const auto& refCountMaterials = refCounts[ResourceType::Material];
+      for(uint32_t i = 0, iEnd = refCountMaterials.Size(); i != iEnd; ++i)
+      {
+        auto  refCount  = refCountMaterials[i];
+        auto& iMaterial = mMaterials[i];
+        if(refCount > 0 && (kForceLoad || !iMaterial.second))
+        {
+          if(iMaterial.first.mRawData)
+          {
+            iMaterial.second = iMaterial.first.Load(mEnvironmentMaps, std::move(*(iMaterial.first.mRawData)));
+          }
+          else
+          {
+            iMaterial.second = TextureSet();
+          }
+        }
+      }
+
+      mResourcesGenerating = false;
+      mResourcesGenerated  = true;
     }
-    else
+    else if(mResourcesGenerated && !mResourcesGenerating)
     {
-      iMaterial.second = TextureSet();
+      mResourcesGenerating = true;
+
+      const auto& refCountShaders = refCounts[ResourceType::Shader];
+      for(uint32_t i = 0, iEnd = refCountShaders.Size(); i != iEnd; ++i)
+      {
+        auto  refCount = refCountShaders[i];
+        auto& iShader  = mShaders[i];
+
+        // Always regenerating the Shader objects as they can't be shared between multiple models.
+        if(refCount > 0 || kForceLoad)
+        {
+          if(iShader.first.mRawData)
+          {
+            iShader.second = iShader.first.Load(std::move(*(iShader.first.mRawData)));
+          }
+          else
+          {
+            iShader.second = Shader();
+          }
+        }
+      }
+
+      mResourcesGenerating = false;
     }
   }
 }
index 75c0c45..95c8609 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef DALI_SCENE3D_LOADERERERERER_RESOURCE_BUNDLE_H_
 #define DALI_SCENE3D_LOADERERERERER_RESOURCE_BUNDLE_H_
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * 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.
  */
 
 // INTERNAL
+#include <dali-scene3d/public-api/loader/buffer-definition.h>
 #include <dali-scene3d/public-api/loader/environment-definition.h>
 #include <dali-scene3d/public-api/loader/material-definition.h>
 #include <dali-scene3d/public-api/loader/mesh-definition.h>
 #include <dali-scene3d/public-api/loader/shader-definition.h>
 #include <dali-scene3d/public-api/loader/skeleton-definition.h>
-#include <dali-scene3d/public-api/loader/buffer-definition.h>
 
 // EXTERNAL
-#include <functional>
-#include <memory>
 #include <dali/public-api/common/vector-wrapper.h>
 #include <dali/public-api/rendering/shader.h>
 #include <dali/public-api/rendering/texture-set.h>
+#include <functional>
+#include <memory>
 
 namespace Dali
 {
@@ -83,7 +83,7 @@ public:
 
   using PathProvider = std::function<std::string(ResourceType::Value)>;
 
-  ResourceBundle() = default;
+  ResourceBundle();
 
   ResourceBundle(const ResourceBundle&) = delete;
   ResourceBundle& operator=(const ResourceBundle&) = delete;
@@ -157,6 +157,12 @@ public: // DATA
 
   SkeletonDefinition::Vector mSkeletons;
   BufferDefinition::Vector   mBuffers;
+
+  bool mRawResourcesLoading;
+  bool mResourcesGenerating;
+
+  bool mRawResourcesLoaded;
+  bool mResourcesGenerated;
 };
 
 } // namespace Loader
index f24d427..2ba2793 100644 (file)
@@ -468,8 +468,10 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
   Vector<ScriptRun>::ConstIterator scriptRunEndIt          = scripts.End();
   bool                             isNewParagraphCharacter = false;
 
-  FontId                  previousEmojiFontId = 0u;
-  TextAbstraction::Script previousScript      = TextAbstraction::UNKNOWN;
+  FontId previousEmojiFontId = 0u;
+  FontId currentFontId       = 0u;
+  FontId previousFontId      = 0u;
+  TextAbstraction::Script previousScript = TextAbstraction::UNKNOWN;
 
   CharacterIndex lastCharacter = startIndex + numberOfCharacters - 1u;
   for(Length index = startIndex; index <= lastCharacter; ++index)
@@ -493,6 +495,7 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
 
     // Get the font for the current character.
     FontId fontId = fontClient.GetFontId(currentFontDescription, currentFontPointSize);
+    currentFontId = fontId;
 
     // Get the script for the current character.
     Script script = GetScript(index,
@@ -554,6 +557,15 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
       }
     }
 
+    if(TextAbstraction::IsSpace(character) &&
+       TextAbstraction::HasLigatureMustBreak(script) &&
+       isValidCachedDefaultFont &&
+       (isDefaultFont || (currentFontId == previousFontId)))
+    {
+      fontId      = cachedDefaultFontId;
+      isValidFont = true;
+    }
+
     // If the given font is not valid, it means either:
     // - there is no cached font for the current script yet or,
     // - the user has set a different font than the default one for the current script or,
@@ -776,6 +788,7 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
     // Whether the current character is a new paragraph character.
     isNewParagraphCharacter = TextAbstraction::IsNewParagraph(character);
     previousScript          = script;
+    previousFontId          = currentFontId;
   } // end traverse characters.
 
   if(0u != currentFontRun.characterRun.numberOfCharacters)
index 3b6a6fc..15c7b41 100644 (file)
@@ -29,7 +29,7 @@ namespace Toolkit
 {
 const unsigned int TOOLKIT_MAJOR_VERSION = 2;
 const unsigned int TOOLKIT_MINOR_VERSION = 2;
-const unsigned int TOOLKIT_MICRO_VERSION = 15;
+const unsigned int TOOLKIT_MICRO_VERSION = 16;
 const char* const  TOOLKIT_BUILD_DATE    = __DATE__ " " __TIME__;
 
 #ifdef DEBUG_ENABLED
index f6ebe63..fb285c7 100644 (file)
@@ -1,6 +1,6 @@
 Name:       dali2-toolkit
 Summary:    Dali 3D engine Toolkit
-Version:    2.2.15
+Version:    2.2.16
 Release:    1
 Group:      System/Libraries
 License:    Apache-2.0 and BSD-3-Clause and MIT