Merge "Fix the normalization factor calculation for blendshapes" into devel/master
authorAdeel Kazmi <adeel.kazmi@samsung.com>
Fri, 19 May 2023 12:49:19 +0000 (12:49 +0000)
committerGerrit Code Review <gerrit@review>
Fri, 19 May 2023 12:49:19 +0000 (12:49 +0000)
76 files changed:
automated-tests/src/dali-scene3d/utc-Dali-Model.cpp
automated-tests/src/dali-scene3d/utc-Dali-NavigationMesh.cpp
automated-tests/src/dali-scene3d/utc-Dali-PathFinding.cpp
automated-tests/src/dali-toolkit-internal/utc-Dali-TextureManager.cpp
automated-tests/src/dali-toolkit/CMakeLists.txt
automated-tests/src/dali-toolkit/utc-Dali-AnimatedVectorImageVisual.cpp
automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp
automated-tests/src/dali-toolkit/utc-Dali-ParticleSystem.cpp [new file with mode: 0644]
build/tizen/CMakeLists.txt
build/tizen/dali-scene3d/CMakeLists.txt
dali-scene3d/internal/algorithm/navigation-mesh-impl.cpp
dali-scene3d/internal/algorithm/navigation-mesh-impl.h
dali-scene3d/internal/algorithm/path-finder-djikstra.cpp
dali-scene3d/internal/algorithm/path-finder-djikstra.h
dali-scene3d/internal/algorithm/path-finder-spfa-double-way.cpp
dali-scene3d/internal/algorithm/path-finder-spfa-double-way.h
dali-scene3d/internal/algorithm/path-finder-spfa.cpp
dali-scene3d/internal/algorithm/path-finder-spfa.h
dali-scene3d/internal/controls/model/model-impl.cpp
dali-scene3d/internal/controls/model/model-impl.h
dali-scene3d/internal/graphics/shaders/default-physically-based-shader.frag
dali-scene3d/internal/graphics/shaders/default-physically-based-shader.vert
dali-scene3d/internal/model-components/model-node-impl.cpp
dali-scene3d/public-api/algorithm/navigation-mesh.cpp
dali-scene3d/public-api/algorithm/navigation-mesh.h
dali-scene3d/public-api/algorithm/path-finder.cpp
dali-scene3d/public-api/algorithm/path-finder.h
dali-scene3d/public-api/loader/mesh-definition.cpp
dali-scene3d/public-api/loader/node-definition.cpp
dali-scene3d/public-api/loader/node-definition.h
dali-scene3d/public-api/loader/scene-definition.cpp
dali-toolkit/devel-api/controls/control-accessible.cpp
dali-toolkit/devel-api/visuals/image-visual-properties-devel.h
dali-toolkit/internal/file.list
dali-toolkit/internal/focus-manager/keyboard-focus-manager-impl.cpp
dali-toolkit/internal/graphics/shaders/color-visual-shader.vert
dali-toolkit/internal/graphics/shaders/gradient-visual-shader.vert
dali-toolkit/internal/graphics/shaders/image-visual-shader.vert
dali-toolkit/internal/particle-system/particle-domain-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-domain-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-emitter-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-emitter-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-list-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-list-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-modifier-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-modifier-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-renderer-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-renderer-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-source-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-source-impl.h [new file with mode: 0644]
dali-toolkit/internal/text/controller/text-controller-event-handler.cpp
dali-toolkit/internal/text/controller/text-controller-impl.cpp
dali-toolkit/internal/text/spannable/span-ranges-container-impl.cpp
dali-toolkit/internal/texture-manager/texture-manager-impl.cpp
dali-toolkit/internal/visuals/animated-vector-image/animated-vector-image-visual.cpp
dali-toolkit/internal/visuals/image/image-visual.cpp
dali-toolkit/public-api/dali-toolkit-version.cpp
dali-toolkit/public-api/file.list
dali-toolkit/public-api/particle-system/particle-domain.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-domain.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-emitter.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-emitter.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-list.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-list.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-modifier.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-modifier.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-renderer.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-renderer.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-source.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-source.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-types.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle.h [new file with mode: 0644]
packaging/dali-toolkit.spec

index e931b3b..c219f93 100644 (file)
@@ -1424,4 +1424,67 @@ int UtcDaliModelFindChildModelNodeByName(void)
   DALI_TEST_EQUALS(child2, modelNode2, TEST_LOCATION);
 
   END_TEST;
-}
\ No newline at end of file
+}
+
+int UtcDaliModelSizeChange(void)
+{
+  tet_infoline(" UtcDaliModelSizeChange.");
+
+  ToolkitTestApplication application;
+
+  Scene3D::Model model = Scene3D::Model::New(TEST_GLTF_FILE_NAME);
+  model.SetProperty(Dali::Actor::Property::SIZE, Vector3(300, 300, 300));
+  application.GetScene().Add(model);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(model.GetChildCount(), 1u, TEST_LOCATION);
+  Vector3 scale = model.GetChildAt(0u).GetProperty<Vector3>(Dali::Actor::Property::SCALE);
+
+  model.SetProperty(Dali::Actor::Property::SIZE, Vector3(600, 600, 600));
+  Vector3 scale2 = model.GetChildAt(0u).GetProperty<Vector3>(Dali::Actor::Property::SCALE);
+
+  DALI_TEST_NOT_EQUALS(scale, scale2, 0.1f, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliModelSizeChange2(void)
+{
+  tet_infoline(" UtcDaliModelSizeChange2.");
+
+  ToolkitTestApplication application;
+
+  Scene3D::Model model = Scene3D::Model::New(TEST_GLTF_FILE_NAME);
+  model.SetProperty(Dali::Actor::Property::SIZE, Vector3(300, 300, 300));
+  application.GetScene().Add(model);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(model.GetChildCount(), 1u, TEST_LOCATION);
+  Vector3 scale = model.GetChildAt(0u).GetProperty<Vector3>(Dali::Actor::Property::SCALE);
+
+  Animation animation = Animation::New(0.5f);
+  animation.AnimateTo(Dali::Property(model, Dali::Actor::Property::SIZE), Vector3(600, 600, 600));
+  animation.Play();
+
+  application.SendNotification();
+  application.Render(250);
+
+  application.SendNotification();
+
+  Vector3 scale2 = model.GetChildAt(0u).GetProperty<Vector3>(Dali::Actor::Property::SCALE);
+  DALI_TEST_NOT_EQUALS(scale, scale2, 0.1f, TEST_LOCATION);
+
+  END_TEST;
+}
index f2f6cd3..957332d 100644 (file)
  *
  */
 
-#include "dali-scene3d/public-api/algorithm/navigation-mesh.h"
-#include "dali-scene3d/public-api/loader/navigation-mesh-factory.h"
 #include <dali-test-suite-utils.h>
 #include <dlfcn.h>
+#include "dali-scene3d/public-api/algorithm/navigation-mesh.h"
+#include "dali-scene3d/public-api/loader/navigation-mesh-factory.h"
 using namespace Dali;
 using namespace Dali::Scene3D::Algorithm;
 using namespace Dali::Scene3D::Loader;
@@ -41,14 +41,14 @@ struct SysOverride
     }
   }
 
-  void SetReturnValue( R value, uint32_t n )
+  void SetReturnValue(R value, uint32_t n)
   {
     if(overrideEnabled)
     {
       tet_infoline("Warning! Overriding return value is already enabled! Ignoring!\n");
       return;
     }
-    result = value;
+    result          = value;
     overrideCounter = n;
     overrideEnabled = true;
   }
@@ -70,29 +70,29 @@ struct SysOverride
   }
 
   std::string funcNameStr;
-  R result{R{}};
-  F* func{nullptr};
-  uint32_t overrideCounter = 0;
-  bool overrideEnabled = false;
+  R           result{R{}};
+  F*          func{nullptr};
+  uint32_t    overrideCounter = 0;
+  bool        overrideEnabled = false;
 };
 
 // Override fseek()
 static thread_local SysOverride<int, decltype(fseek)> call_fseek("fseek");
-extern "C" int fseek (FILE *s, long int o, int w)
+extern "C" int                                        fseek(FILE* s, long int o, int w)
 {
-  return call_fseek.Invoke( s, o, w );
+  return call_fseek.Invoke(s, o, w);
 }
 
 // Override ftell()
 static thread_local SysOverride<int, decltype(ftell)> call_ftell("ftell");
-extern "C" long int ftell(FILE *s)
+extern "C" long int                                   ftell(FILE* s)
 {
-  return call_ftell.Invoke( s );
+  return call_ftell.Invoke(s);
 }
 
 // Override fread()
 static thread_local SysOverride<int, decltype(fread)> call_fread("fread");
-extern "C" size_t fread (void *__restrict p, size_t s, size_t n, FILE *__restrict st)
+extern "C" size_t                                     fread(void* __restrict p, size_t s, size_t n, FILE* __restrict st)
 {
   return call_fread.Invoke(p, s, n, st);
 }
@@ -176,15 +176,15 @@ int UtcDaliNavigationMeshCreateFromBufferP(void)
 {
   tet_infoline("UtcDaliNavigationMeshCreateFromBufferP: Creates navigation mesh using binary buffer");
 
-  auto fin = fopen("resources/navmesh-test.bin", "rb");
-  [[maybe_unused]] auto err = fseek(fin, 0, SEEK_END);
-  auto length = ftell(fin);
-  fseek( fin, 0, SEEK_SET);
+  auto                  fin    = fopen("resources/navmesh-test.bin", "rb");
+  [[maybe_unused]] auto err    = fseek(fin, 0, SEEK_END);
+  auto                  length = ftell(fin);
+  fseek(fin, 0, SEEK_SET);
   std::vector<uint8_t> buffer;
   buffer.resize(length);
-  fread( buffer.data(), 1, length, fin );
+  fread(buffer.data(), 1, length, fin);
   fclose(fin);
-  auto result = NavigationMeshFactory::CreateFromBuffer( buffer );
+  auto result = NavigationMeshFactory::CreateFromBuffer(buffer);
   DALI_TEST_CHECK(result != nullptr);
 
   END_TEST;
@@ -199,12 +199,12 @@ int UtcDaliNavigationMeshCountersP(void)
   DALI_TEST_CHECK(result != nullptr);
 
   auto vertexCount = result->GetVertexCount();
-  auto edgeCount = result->GetEdgeCount();
-  auto faceCount = result->GetFaceCount();
+  auto edgeCount   = result->GetEdgeCount();
+  auto faceCount   = result->GetFaceCount();
 
-  DALI_TEST_EQUALS( vertexCount, 132, TEST_LOCATION );
-  DALI_TEST_EQUALS( edgeCount, 300, TEST_LOCATION );
-  DALI_TEST_EQUALS( faceCount, 165, TEST_LOCATION );
+  DALI_TEST_EQUALS(vertexCount, 132, TEST_LOCATION);
+  DALI_TEST_EQUALS(edgeCount, 300, TEST_LOCATION);
+  DALI_TEST_EQUALS(faceCount, 165, TEST_LOCATION);
 
   END_TEST;
 }
@@ -219,7 +219,7 @@ int UtcDaliNavigationMeshGetVertexP(void)
 
   auto vertexCount = navmesh->GetVertexCount();
 
-  DALI_TEST_EQUALS( vertexCount, 132, TEST_LOCATION );
+  DALI_TEST_EQUALS(vertexCount, 132, TEST_LOCATION);
 
   // List of coords, must be verified with Blender exporter
   // clang-format off
@@ -235,12 +235,12 @@ int UtcDaliNavigationMeshGetVertexP(void)
   // clang-format on
 
   auto j = 0;
-  for( auto i = 0u; i < 132; i+= 10, j+= 3)
+  for(auto i = 0u; i < 132; i += 10, j += 3)
   {
     const auto* vertex = navmesh->GetVertex(i);
-    Vector3 v0(vertex->co);
-    Vector3 v1(vertexData[j], vertexData[j+1], vertexData[j+2]);
-    DALI_TEST_EQUALS( v0, v1, TEST_LOCATION);
+    Vector3     v0(vertex->coordinates);
+    Vector3     v1(vertexData[j], vertexData[j + 1], vertexData[j + 2]);
+    DALI_TEST_EQUALS(v0, v1, TEST_LOCATION);
   }
 
   END_TEST;
@@ -256,11 +256,11 @@ int UtcDaliNavigationMeshGetEdgeP(void)
 
   auto edgeCount = navmesh->GetEdgeCount();
 
-  DALI_TEST_EQUALS( edgeCount, 300, TEST_LOCATION );
+  DALI_TEST_EQUALS(edgeCount, 300, TEST_LOCATION);
 
   // List of coords, must be verified with Blender exporter
   // clang-format off
-  std::vector<uint16_t> edgeData = {
+  std::vector<EdgeIndex> edgeData = {
     2, 65535, 8, 1,
     8, 109, 124, 108,
     10, 158, 32, 35,
@@ -274,18 +274,18 @@ int UtcDaliNavigationMeshGetEdgeP(void)
   };
   // clang-format on
   auto j = 0;
-  for( auto i = 0u; i < 300; i+= 30, j+= 4)
+  for(auto i = 0u; i < 300; i += 30, j += 4)
   {
     const auto* edge = navmesh->GetEdge(i);
-    auto e0 = edge->face[0];
-    auto e1 = edge->face[1];
-    auto v0 = edge->vertex[0];
-    auto v1 = edge->vertex[1];
-
-    DALI_TEST_EQUALS(e0, edgeData[j+0], TEST_LOCATION);
-    DALI_TEST_EQUALS(e1, edgeData[j+1], TEST_LOCATION);
-    DALI_TEST_EQUALS(v0, edgeData[j+2], TEST_LOCATION);
-    DALI_TEST_EQUALS(v1, edgeData[j+3], TEST_LOCATION);
+    auto        e0   = edge->face[0];
+    auto        e1   = edge->face[1];
+    auto        v0   = edge->vertex[0];
+    auto        v1   = edge->vertex[1];
+
+    DALI_TEST_EQUALS(e0, edgeData[j + 0], TEST_LOCATION);
+    DALI_TEST_EQUALS(e1, edgeData[j + 1], TEST_LOCATION);
+    DALI_TEST_EQUALS(v0, edgeData[j + 2], TEST_LOCATION);
+    DALI_TEST_EQUALS(v1, edgeData[j + 3], TEST_LOCATION);
   }
 
   END_TEST;
@@ -301,7 +301,7 @@ int UtcDaliNavigationMeshGetFaceP(void)
 
   auto faceCount = navmesh->GetFaceCount();
 
-  DALI_TEST_EQUALS( faceCount, 165, TEST_LOCATION );
+  DALI_TEST_EQUALS(faceCount, 165, TEST_LOCATION);
 
   // List of coords, must be verified with Blender exporter
   // clang-format off
@@ -321,31 +321,30 @@ int UtcDaliNavigationMeshGetFaceP(void)
   };
   // clang-format on
   auto j = 0;
-  for( auto i = 0u; i < 165; i+= 16, j++)
+  for(auto i = 0u; i < 165; i += 16, j++)
   {
     const auto* face = navmesh->GetFace(i);
-    Vector3 n0(face->normal);
-    Vector3 c0(face->center);
+    Vector3     n0(face->normal);
+    Vector3     c0(face->center);
 
     Vector3 n1(faceData[j].normal);
     Vector3 c1(faceData[j].center);
 
-    DALI_TEST_EQUALS( n0, n1, TEST_LOCATION);
-    DALI_TEST_EQUALS( c0, c1, TEST_LOCATION);
+    DALI_TEST_EQUALS(n0, n1, TEST_LOCATION);
+    DALI_TEST_EQUALS(c0, c1, TEST_LOCATION);
 
-    DALI_TEST_EQUALS( faceData[j].vertex[0], face->vertex[0], TEST_LOCATION);
-    DALI_TEST_EQUALS( faceData[j].vertex[1], face->vertex[1], TEST_LOCATION);
-    DALI_TEST_EQUALS( faceData[j].vertex[2], face->vertex[2], TEST_LOCATION);
+    DALI_TEST_EQUALS(faceData[j].vertex[0], face->vertex[0], TEST_LOCATION);
+    DALI_TEST_EQUALS(faceData[j].vertex[1], face->vertex[1], TEST_LOCATION);
+    DALI_TEST_EQUALS(faceData[j].vertex[2], face->vertex[2], TEST_LOCATION);
 
-    DALI_TEST_EQUALS( faceData[j].edge[0], face->edge[0], TEST_LOCATION);
-    DALI_TEST_EQUALS( faceData[j].edge[1], face->edge[1], TEST_LOCATION);
-    DALI_TEST_EQUALS( faceData[j].edge[2], face->edge[2], TEST_LOCATION);
+    DALI_TEST_EQUALS(faceData[j].edge[0], face->edge[0], TEST_LOCATION);
+    DALI_TEST_EQUALS(faceData[j].edge[1], face->edge[1], TEST_LOCATION);
+    DALI_TEST_EQUALS(faceData[j].edge[2], face->edge[2], TEST_LOCATION);
   }
 
   END_TEST;
 }
 
-
 int UtcDaliNavigationGetGravityP(void)
 {
   tet_infoline("UtcDaliNavigationGetGravityP: Tests gravity vector");
@@ -353,9 +352,9 @@ int UtcDaliNavigationGetGravityP(void)
   auto gravity = navmesh->GetGravityVector();
 
   // navmesh-test.bin is exported in Blender and the default gravity is Z = -1
-  Dali::Vector3 expectedGravity( 0.0f, 0.0f, -1.0f );
+  Dali::Vector3 expectedGravity(0.0f, 0.0f, -1.0f);
 
-  DALI_TEST_EQUALS( gravity, expectedGravity, TEST_LOCATION);
+  DALI_TEST_EQUALS(gravity, expectedGravity, TEST_LOCATION);
 
   END_TEST;
 }
@@ -368,9 +367,9 @@ int UtcDaliNavigationSetTransformP(void)
 
   Matrix matrix;
   matrix.SetIdentity();
-  Quaternion q = Quaternion( Radian(Degree(-90)), Vector3(1.0, 0.0, 0.0));
-  Matrix newMatrix;
-  matrix.Multiply( newMatrix, matrix, q); // Rotate matrix along X-axis
+  Quaternion q = Quaternion(Radian(Degree(-90)), Vector3(1.0, 0.0, 0.0));
+  Matrix     newMatrix;
+  matrix.Multiply(newMatrix, matrix, q); // Rotate matrix along X-axis
 
   navmesh->SetSceneTransform(newMatrix);
 
@@ -384,12 +383,12 @@ int UtcDaliNavigationSetTransformP(void)
   auto gravityVector = navmesh->GetGravityVector();
 
   // 'point' should be turned into the gravity vector after transforming into the local space
-  DALI_TEST_EQUALS( navMeshLocalSpace, gravityVector, TEST_LOCATION);
+  DALI_TEST_EQUALS(navMeshLocalSpace, gravityVector, TEST_LOCATION);
 
   navMeshParentSpace = navmesh->PointLocalToScene(gravityVector);
 
   // The gravity should be transformed back into point
-  DALI_TEST_EQUALS( navMeshParentSpace, point, TEST_LOCATION);
+  DALI_TEST_EQUALS(navMeshParentSpace, point, TEST_LOCATION);
 
   END_TEST;
 }
@@ -403,21 +402,21 @@ int UtcDaliNavigationFindFloor0P(void)
   // All calculations in the navmesh local space
   navmesh->SetSceneTransform(Matrix(Matrix::IDENTITY));
 
-  std::vector<Vector3> inPositions;
-  std::vector<Vector3> expectedPositions;
-  std::vector<uint32_t> expectedFaceIndex;
-  std::vector<bool> expectedResult;
+  std::vector<Vector3>   inPositions;
+  std::vector<Vector3>   expectedPositions;
+  std::vector<FaceIndex> expectedFaceIndex;
+  std::vector<bool>      expectedResult;
 
   // Lift slightly over the floor level
   auto upFromGravity = navmesh->GetGravityVector() * (0.05f);
 
   auto size = navmesh->GetFaceCount();
-  for( auto i = 0u; i < size; ++i)
+  for(auto i = 0u; i < size; ++i)
   {
     const auto* face = navmesh->GetFace(i);
     Vector3(face->center);
     inPositions.emplace_back(Vector3(face->center));
-    inPositions.back() -= Vector3( upFromGravity );
+    inPositions.back() -= Vector3(upFromGravity);
     expectedResult.emplace_back(true);
 
     expectedPositions.emplace_back(face->center);
@@ -428,29 +427,29 @@ int UtcDaliNavigationFindFloor0P(void)
   // Middle 'circle' of scene
   inPositions.emplace_back(Vector3(-0.048838f, 0.039285f, 0.013085f));
   expectedPositions.emplace_back();
-  expectedFaceIndex.emplace_back( NavigationMesh::NULL_FACE );
+  expectedFaceIndex.emplace_back(NavigationMesh::NULL_FACE);
   expectedResult.emplace_back(false);
 
   // Triangle under stairs
   inPositions.emplace_back(Vector3(0.44365f, -1.787f, 0.13085f));
   expectedPositions.emplace_back();
-  expectedFaceIndex.emplace_back( NavigationMesh::NULL_FACE );
+  expectedFaceIndex.emplace_back(NavigationMesh::NULL_FACE);
   expectedResult.emplace_back(false);
 
   // Outside area
   inPositions.emplace_back(Vector3(0.77197f, -3.8596f, 0.13085f));
   expectedPositions.emplace_back();
-  expectedFaceIndex.emplace_back( NavigationMesh::NULL_FACE );
+  expectedFaceIndex.emplace_back(NavigationMesh::NULL_FACE);
   expectedResult.emplace_back(false);
 
-  for( auto i = 0u; i < inPositions.size(); ++i )
+  for(auto i = 0u; i < inPositions.size(); ++i)
   {
-    Vector3  outPosition;
-    uint32_t faceIndex {NavigationMesh::NULL_FACE};
-    auto result = navmesh->FindFloor(inPositions[i], outPosition, faceIndex);
-    DALI_TEST_EQUALS( bool(result), bool(expectedResult[i]), TEST_LOCATION);
-    DALI_TEST_EQUALS( faceIndex, expectedFaceIndex[i], TEST_LOCATION);
-    DALI_TEST_EQUALS( outPosition, expectedPositions[i], TEST_LOCATION);
+    Vector3   outPosition;
+    FaceIndex faceIndex{NavigationMesh::NULL_FACE};
+    auto      result = navmesh->FindFloor(inPositions[i], outPosition, faceIndex);
+    DALI_TEST_EQUALS(bool(result), bool(expectedResult[i]), TEST_LOCATION);
+    DALI_TEST_EQUALS(faceIndex, expectedFaceIndex[i], TEST_LOCATION);
+    DALI_TEST_EQUALS(outPosition, expectedPositions[i], TEST_LOCATION);
   }
 
   END_TEST;
@@ -466,49 +465,49 @@ int UtcDaliNavigationFindFloorForFace1P(void)
   navmesh->SetSceneTransform(Matrix(Matrix::IDENTITY));
 
   {
-    auto faceIndex = 0u;
-    auto position = Vector3( 0, 0, 0);
+    auto faceIndex           = FaceIndex(0u);
+    auto position            = Vector3(0, 0, 0);
     auto dontCheckNeighbours = true;
-    auto outPosition = Vector3();
-    auto expectedPosition = Vector3();
-    bool result = false;
+    auto outPosition         = Vector3();
+    auto expectedPosition    = Vector3();
+    bool result              = false;
 
     {
       // test 1. position lies within selected triangle
-      faceIndex = 137;
-      position = Vector3(-6.0767f, -1.7268f, 4.287f);
-      expectedPosition = Vector3(-6.0767f, -1.7268f, 3.83f);
+      faceIndex           = 137;
+      position            = Vector3(-6.0767f, -1.7268f, 4.287f);
+      expectedPosition    = Vector3(-6.0767f, -1.7268f, 3.83f);
       dontCheckNeighbours = true;
-      result = navmesh->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
+      result              = navmesh->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
 
-      DALI_TEST_EQUALS( result, true, TEST_LOCATION);
-      DALI_TEST_EQUALS( outPosition, expectedPosition, TEST_LOCATION);
+      DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+      DALI_TEST_EQUALS(outPosition, expectedPosition, TEST_LOCATION);
     }
 
     {
       // test 2. position lies outside selected triangle, not checking neighbours
-      faceIndex = 137;
-      position = Vector3(-5.3073f, -0.6023f, 4.287f);
-      expectedPosition = Vector3::ZERO;
-      outPosition = Vector3::ZERO;
+      faceIndex           = 137;
+      position            = Vector3(-5.3073f, -0.6023f, 4.287f);
+      expectedPosition    = Vector3::ZERO;
+      outPosition         = Vector3::ZERO;
       dontCheckNeighbours = true;
-      result = navmesh->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
+      result              = navmesh->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
 
-      DALI_TEST_EQUALS( result, false, TEST_LOCATION);
-      DALI_TEST_EQUALS( outPosition, expectedPosition, TEST_LOCATION);
+      DALI_TEST_EQUALS(result, false, TEST_LOCATION);
+      DALI_TEST_EQUALS(outPosition, expectedPosition, TEST_LOCATION);
     }
 
     {
       // test 3. position lies outside selected triangle but this time checking neighbours
-      faceIndex = 137;
-      position = Vector3(-5.3073f, -0.6023f, 4.287f);
-      expectedPosition = Vector3(-5.3073, -0.6023, 3.83);
-      outPosition = Vector3::ZERO;
+      faceIndex           = 137;
+      position            = Vector3(-5.3073f, -0.6023f, 4.287f);
+      expectedPosition    = Vector3(-5.3073, -0.6023, 3.83);
+      outPosition         = Vector3::ZERO;
       dontCheckNeighbours = false;
-      result = navmesh->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
+      result              = navmesh->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
 
-      DALI_TEST_EQUALS( result, true, TEST_LOCATION);
-      DALI_TEST_EQUALS( outPosition, expectedPosition, TEST_LOCATION);
+      DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+      DALI_TEST_EQUALS(outPosition, expectedPosition, TEST_LOCATION);
     }
   }
 
@@ -525,26 +524,25 @@ int UtcDaliNavigationFindFloorForFace2P(void)
   navmesh->SetSceneTransform(Matrix(Matrix::IDENTITY));
 
   {
-    [[maybe_unused]] auto faceIndex = 0u;
-    auto position = Vector3( 0, 0, 0);
-    auto dontCheckNeighbours = true;
-    auto outPosition = Vector3();
-    auto expectedPosition = Vector3();
-    bool result = false;
+    [[maybe_unused]] auto faceIndex           = 0u;
+    auto                  position            = Vector3(0, 0, 0);
+    auto                  dontCheckNeighbours = true;
+    auto                  outPosition         = Vector3();
+    auto                  expectedPosition    = Vector3();
+    bool                  result              = false;
 
     {
       // test 4. position lies within a triangle but this time full search forced,
       // the navmesh must have no previous searches (mCurrentFace shouldn't be set)
-      faceIndex = 137;
-      position = Vector3(-6.0767f, -1.7268f, 4.287f);
-      expectedPosition = Vector3(-6.0767f, -1.7268f, 3.83f);
+      faceIndex           = 137;
+      position            = Vector3(-6.0767f, -1.7268f, 4.287f);
+      expectedPosition    = Vector3(-6.0767f, -1.7268f, 3.83f);
       dontCheckNeighbours = true;
-      result = navmesh->FindFloorForFace(position, NavigationMesh::NULL_FACE, dontCheckNeighbours, outPosition);
+      result              = navmesh->FindFloorForFace(position, NavigationMesh::NULL_FACE, dontCheckNeighbours, outPosition);
 
-      DALI_TEST_EQUALS( result, true, TEST_LOCATION);
-      DALI_TEST_EQUALS( outPosition, expectedPosition, TEST_LOCATION);
+      DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+      DALI_TEST_EQUALS(outPosition, expectedPosition, TEST_LOCATION);
     }
-
   }
 
   END_TEST;
index 8866f36..b0cfeb4 100644 (file)
  *
  */
 
+#include <dali-test-suite-utils.h>
 #include "dali-scene3d/public-api/algorithm/navigation-mesh.h"
 #include "dali-scene3d/public-api/algorithm/path-finder.h"
 #include "dali-scene3d/public-api/loader/navigation-mesh-factory.h"
-#include <dali-test-suite-utils.h>
 
 using namespace Dali;
 using namespace Dali::Scene3D::Algorithm;
 using namespace Dali::Scene3D::Loader;
 
-bool CompareResults( const std::vector<uint32_t>& nodes, const WayPointList& waypoints )
+bool CompareResults(const std::vector<FaceIndex>& nodes, const WayPointList& waypoints)
 {
   if(nodes.size() != waypoints.size())
   {
@@ -41,10 +41,10 @@ bool CompareResults( const std::vector<uint32_t>& nodes, const WayPointList& way
       oss << waypoint.GetNavigationMeshFaceIndex() << ", ";
     }
     oss << "]\n";
-    tet_printf("%s\n",oss.str().c_str());
+    tet_printf("%s\n", oss.str().c_str());
     return false;
   }
-  for(auto i = 0u; i < nodes.size(); ++i )
+  for(auto i = 0u; i < nodes.size(); ++i)
   {
     if(nodes[i] != waypoints[i].GetNavigationMeshFaceIndex())
     {
@@ -61,7 +61,7 @@ bool CompareResults( const std::vector<uint32_t>& nodes, const WayPointList& way
         oss << waypoint.GetNavigationMeshFaceIndex() << ", ";
       }
       oss << "]\n";
-      tet_printf("%s\n",oss.str().c_str());
+      tet_printf("%s\n", oss.str().c_str());
       return false;
     }
   }
@@ -70,9 +70,9 @@ bool CompareResults( const std::vector<uint32_t>& nodes, const WayPointList& way
 
 int UtcDaliPathFinderNewP(void)
 {
-  auto navmesh = NavigationMeshFactory::CreateFromFile( "resources/navmesh-test.bin");
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
 
-  auto pathfinder = PathFinder::New( *navmesh, PathFinderAlgorithm::DEFAULT );
+  auto pathfinder = PathFinder::New(*navmesh, PathFinderAlgorithm::DEFAULT);
 
   DALI_TEST_CHECK(navmesh);
   DALI_TEST_CHECK(pathfinder);
@@ -82,9 +82,9 @@ int UtcDaliPathFinderNewP(void)
 
 int UtcDaliPathFinderNewFail(void)
 {
-  auto navmesh = NavigationMeshFactory::CreateFromFile( "resources/navmesh-test.bin");
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
 
-  auto pathfinder = PathFinder::New( *navmesh, static_cast<PathFinderAlgorithm>(-1) );
+  auto pathfinder = PathFinder::New(*navmesh, static_cast<PathFinderAlgorithm>(-1));
 
   DALI_TEST_CHECK(navmesh);
   DALI_TEST_CHECK(!pathfinder);
@@ -92,21 +92,21 @@ int UtcDaliPathFinderNewFail(void)
   END_TEST;
 }
 
-void printWaypointForPython( WayPointList& waypoints)
+void printWaypointForPython(WayPointList& waypoints)
 {
-  tet_printf( "size: %d\n", waypoints.size());
-  tet_printf( "[");
+  tet_printf("size: %d\n", waypoints.size());
+  tet_printf("[");
   for(auto& wp : waypoints)
   {
     auto index = wp.GetNavigationMeshFaceIndex();
     tet_printf("%d, ", index);
   }
-  tet_printf( "]");
+  tet_printf("]");
 }
 
 int UtcDaliPathFinderFindShortestPath0(void)
 {
-  auto navmesh = NavigationMeshFactory::CreateFromFile( "resources/navmesh-test.bin");
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
 
   std::vector<PathFinderAlgorithm> testAlgorithms = {
     PathFinderAlgorithm::DJIKSTRA_SHORTEST_PATH,
@@ -116,7 +116,7 @@ int UtcDaliPathFinderFindShortestPath0(void)
   for(const auto& algorithm : testAlgorithms)
   {
     tet_printf("Test algorithm type : %d\n", static_cast<int>(algorithm));
-    auto pathfinder = PathFinder::New( *navmesh, algorithm );
+    auto pathfinder = PathFinder::New(*navmesh, algorithm);
 
     DALI_TEST_CHECK(navmesh);
     DALI_TEST_CHECK(pathfinder);
@@ -126,7 +126,7 @@ int UtcDaliPathFinderFindShortestPath0(void)
       DALI_TEST_NOT_EQUALS(int(waypoints.size()), 0, 0, TEST_LOCATION);
 
       // Results are verified in the Blender
-      std::vector<uint32_t> expectedResults =
+      std::vector<FaceIndex> expectedResults =
         {18, 97, 106, 82, 50, 139};
 
       DALI_TEST_EQUALS(CompareResults(expectedResults, waypoints), true, TEST_LOCATION);
@@ -142,11 +142,10 @@ int UtcDaliPathFinderFindShortestPath0(void)
       //printWaypointForPython(waypoints);
 
       // Results are verified in the Blender
-      std::vector<uint32_t> expectedResults =
+      std::vector<FaceIndex> expectedResults =
         {18, 97, 106, 82, 50, 6, 89, 33, 157};
 
       DALI_TEST_EQUALS(CompareResults(expectedResults, waypoints), true, TEST_LOCATION);
-
     }
   }
 
@@ -155,9 +154,9 @@ int UtcDaliPathFinderFindShortestPath0(void)
 
 int UtcDaliPathFinderFindShortestPath1(void)
 {
-  auto navmesh = NavigationMeshFactory::CreateFromFile( "resources/navmesh-test.bin");
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
   // All coordinates in navmesh local space
-  navmesh->SetSceneTransform( Matrix(Matrix::IDENTITY));
+  navmesh->SetSceneTransform(Matrix(Matrix::IDENTITY));
 
   std::vector<PathFinderAlgorithm> testAlgorithms = {
     PathFinderAlgorithm::DJIKSTRA_SHORTEST_PATH,
@@ -168,29 +167,29 @@ int UtcDaliPathFinderFindShortestPath1(void)
   for(const auto& algorithm : testAlgorithms)
   {
     tet_printf("Test algorithm type : %d\n", static_cast<int>(algorithm));
-    auto pathfinder = PathFinder::New( *navmesh, algorithm );
+    auto pathfinder = PathFinder::New(*navmesh, algorithm);
 
     DALI_TEST_CHECK(navmesh);
     DALI_TEST_CHECK(pathfinder);
 
     {
       Vector3 from(-6.0767, -1.7268, 0.1438); // ground floor
-      Vector3 to(-6.0767, -1.7268, 4.287); // first floor
+      Vector3 to(-6.0767, -1.7268, 4.287);    // first floor
 
       auto waypoints = pathfinder->FindPath(from, to);
       DALI_TEST_NOT_EQUALS(int(waypoints.size()), 0, 0, TEST_LOCATION);
 
       // Results are verified in the Blender
-      std::vector<uint32_t> expectedResults =
-      {154, 58, 85, 106, 128, 132, 137};
+      std::vector<FaceIndex> expectedResults =
+        {154, 58, 85, 106, 128, 132, 137};
 
       DALI_TEST_EQUALS(CompareResults(expectedResults, waypoints), true, TEST_LOCATION);
 
       // Verify last and first points by finding floor points
       {
-        Vector3  verifyPos = Vector3::ZERO;
-        uint32_t verifyIndex  = NavigationMesh::NULL_FACE;
-        auto     result = navmesh->FindFloor(from, verifyPos, verifyIndex);
+        Vector3   verifyPos   = Vector3::ZERO;
+        FaceIndex verifyIndex = NavigationMesh::NULL_FACE;
+        auto      result      = navmesh->FindFloor(from, verifyPos, verifyIndex);
 
         DALI_TEST_EQUALS(result, true, TEST_LOCATION);
         DALI_TEST_EQUALS(verifyPos, waypoints[0].GetScenePosition(), TEST_LOCATION);
@@ -202,9 +201,9 @@ int UtcDaliPathFinderFindShortestPath1(void)
       }
 
       {
-        Vector3  verifyPos = Vector3::ZERO;
-        uint32_t verifyIndex  = NavigationMesh::NULL_FACE;
-        auto     result = navmesh->FindFloor(to, verifyPos, verifyIndex);
+        Vector3   verifyPos   = Vector3::ZERO;
+        FaceIndex verifyIndex = NavigationMesh::NULL_FACE;
+        auto      result      = navmesh->FindFloor(to, verifyPos, verifyIndex);
 
         DALI_TEST_EQUALS(result, true, TEST_LOCATION);
         DALI_TEST_EQUALS(verifyPos, waypoints.back().GetScenePosition(), TEST_LOCATION);
index 8c63d53..bd3d5ed 100644 (file)
@@ -28,6 +28,8 @@
 #include <dali-toolkit/internal/texture-manager/texture-upload-observer.h>
 #include <dali-toolkit/internal/visuals/image-atlas-manager.h>
 #include <dali-toolkit/internal/visuals/visual-factory-impl.h> ///< For VisualFactory's member TextureManager.
+#include <dali-toolkit/public-api/image-loader/image-url.h>
+#include <dali-toolkit/public-api/image-loader/image.h>
 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
 
 #include <test-encoded-image-buffer.h>
@@ -435,6 +437,152 @@ int UtcTextureManagerEncodedImageBuffer(void)
   END_TEST;
 }
 
+int UtcTextureManagerExternalTexture(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("UtcTextureManagerExternalTexture check TextureManager using external texture works well");
+
+  auto  visualFactory  = Toolkit::VisualFactory::Get();
+  auto& textureManager = GetImplementation(visualFactory).GetTextureManager(); // Use VisualFactory's texture manager
+
+  TestObserver observer1;
+  TestObserver observer2;
+
+  auto                               textureId1(TextureManager::INVALID_TEXTURE_ID);
+  auto                               textureId2(TextureManager::INVALID_TEXTURE_ID);
+  std::string                        maskname(TEST_MASK_FILE_NAME);
+  TextureManager::MaskingDataPointer maskInfo = nullptr;
+  maskInfo.reset(new TextureManager::MaskingData());
+  maskInfo->mAlphaMaskUrl       = maskname;
+  maskInfo->mAlphaMaskId        = TextureManager::INVALID_TEXTURE_ID;
+  maskInfo->mCropToMask         = true;
+  maskInfo->mContentScaleFactor = 1.0f;
+  Vector4                       atlasRect(0.f, 0.f, 1.f, 1.f);
+  Dali::ImageDimensions         atlasRectSize(0, 0);
+  bool                          synchronousLoading(false);
+  bool                          atlasingStatus(false);
+  bool                          loadingStatus(false);
+  auto                          preMultiply         = TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
+  ImageAtlasManagerPtr          atlasManager        = nullptr;
+  Toolkit::AtlasUploadObserver* atlasUploadObserver = nullptr;
+
+  uint32_t width(64);
+  uint32_t height(64);
+  uint32_t bufferSize = width * height * Pixel::GetBytesPerPixel(Pixel::RGBA8888);
+
+  uint8_t*  buffer    = reinterpret_cast<uint8_t*>(malloc(bufferSize));
+  PixelData pixelData = PixelData::New(buffer, bufferSize, width, height, Pixel::RGBA8888, PixelData::FREE);
+
+  DALI_TEST_CHECK(pixelData);
+
+  Dali::Toolkit::ImageUrl imageUrl = Dali::Toolkit::Image::GenerateUrl(pixelData, true);
+  std::string             url      = imageUrl.GetUrl();
+
+  TextureSet texture1 = textureManager.LoadTexture(
+    url,
+    ImageDimensions(),
+    FittingMode::SCALE_TO_FILL,
+    SamplingMode::BOX_THEN_LINEAR,
+    maskInfo,
+    synchronousLoading,
+    textureId1,
+    atlasRect,
+    atlasRectSize,
+    atlasingStatus,
+    loadingStatus,
+    &observer1,
+    atlasUploadObserver,
+    atlasManager,
+    true,
+    TextureManager::ReloadPolicy::CACHED,
+    preMultiply);
+
+  TextureSet texture2 = textureManager.LoadTexture(
+    url,
+    ImageDimensions(),
+    FittingMode::SCALE_TO_FILL,
+    SamplingMode::BOX_THEN_LINEAR,
+    maskInfo,
+    synchronousLoading,
+    textureId2,
+    atlasRect,
+    atlasRectSize,
+    atlasingStatus,
+    loadingStatus,
+    &observer2,
+    atlasUploadObserver,
+    atlasManager,
+    true,
+    TextureManager::ReloadPolicy::CACHED,
+    preMultiply);
+
+  DALI_TEST_EQUALS(observer1.mLoaded, false, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer1.mObserverCalled, false, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer2.mLoaded, false, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer2.mObserverCalled, false, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(observer1.mLoaded, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer1.mObserverCalled, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer1.mCompleteType, TestObserver::CompleteType::UPLOAD_COMPLETE, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(observer2.mLoaded, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer2.mObserverCalled, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(observer2.mCompleteType, TestObserver::CompleteType::UPLOAD_COMPLETE, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(textureId1 == textureId2, true, TEST_LOCATION);
+
+  texture1 = textureManager.LoadTexture(
+    url,
+    ImageDimensions(),
+    FittingMode::SCALE_TO_FILL,
+    SamplingMode::BOX_THEN_LINEAR,
+    maskInfo,
+    synchronousLoading,
+    textureId1,
+    atlasRect,
+    atlasRectSize,
+    atlasingStatus,
+    loadingStatus,
+    &observer1,
+    atlasUploadObserver,
+    atlasManager,
+    true,
+    TextureManager::ReloadPolicy::CACHED,
+    preMultiply);
+
+  texture2 = textureManager.LoadTexture(
+    url,
+    ImageDimensions(),
+    FittingMode::SCALE_TO_FILL,
+    SamplingMode::BOX_THEN_LINEAR,
+    maskInfo,
+    synchronousLoading,
+    textureId2,
+    atlasRect,
+    atlasRectSize,
+    atlasingStatus,
+    loadingStatus,
+    &observer2,
+    atlasUploadObserver,
+    atlasManager,
+    true,
+    TextureManager::ReloadPolicy::CACHED,
+    preMultiply);
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(textureId1 == textureId2, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(texture1 != texture2, true, TEST_LOCATION);
+
+  END_TEST;
+}
+
 int UtcTextureManagerEncodedImageBufferReferenceCount(void)
 {
   ToolkitTestApplication application;
index d07288b..8ce883d 100755 (executable)
@@ -31,6 +31,7 @@ SET(TC_SOURCES
   utc-Dali-JsonParser.cpp
   utc-Dali-KeyInputFocusManager.cpp
   utc-Dali-PageTurnView.cpp
+  utc-Dali-ParticleSystem.cpp
   utc-Dali-Scene3dView.cpp
   utc-Dali-ScrollBar.cpp
   utc-Dali-ScrollView.cpp
@@ -183,7 +184,7 @@ ADD_CUSTOM_COMMAND(
 ADD_EXECUTABLE(${EXEC_NAME} ${EXEC_NAME}.h ${EXEC_NAME}.cpp ${TC_SOURCES} ${TEST_HARNESS_SOURCES})
 TARGET_LINK_LIBRARIES(${EXEC_NAME}
     ${${CAPI_LIB}_LIBRARIES}
-    -lpthread --coverage
+    -lpthread --coverage -ldl
 )
 
 INSTALL(PROGRAMS ${EXEC_NAME}
index 1c9daf7..92cc203 100644 (file)
@@ -838,6 +838,7 @@ int UtcDaliAnimatedVectorImageVisualPlayRangeMarker(void)
   ToolkitTestApplication application;
   tet_infoline("UtcDaliAnimatedVectorImageVisualPlayRangeMarker");
 
+  // Set 1 marker as array
   Property::Array array;
   array.PushBack(VECTOR_ANIMATION_MARKER_NAME_1);
 
@@ -884,6 +885,31 @@ int UtcDaliAnimatedVectorImageVisualPlayRangeMarker(void)
   DALI_TEST_EQUALS(VECTOR_ANIMATION_MARKER_START_FRAME_1, resultStartFrame, TEST_LOCATION);
   DALI_TEST_EQUALS(VECTOR_ANIMATION_MARKER_END_FRAME_1, resultEndFrame, TEST_LOCATION);
 
+  // Set 1 marker as string
+  array.Clear();
+
+  attributes.Clear();
+  attributes.Add(DevelImageVisual::Property::PLAY_RANGE, VECTOR_ANIMATION_MARKER_NAME_1);
+  DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelVisual::Action::UPDATE_PROPERTY, attributes);
+
+  // To make event trigger
+  actor.SetProperty(Actor::Property::SIZE, Vector2(40.0f, 40.0f));
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+  map   = actor.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
+  value = map.Find(DevelImageVisual::Property::PLAY_RANGE);
+
+  result = value->GetArray();
+  result->GetElementAt(0).Get(resultStartFrame);
+  result->GetElementAt(1).Get(resultEndFrame);
+
+  DALI_TEST_EQUALS(VECTOR_ANIMATION_MARKER_START_FRAME_1, resultStartFrame, TEST_LOCATION);
+  DALI_TEST_EQUALS(VECTOR_ANIMATION_MARKER_END_FRAME_1, resultEndFrame, TEST_LOCATION);
+
   // Set 2 markers
   array.Clear();
   array.PushBack(VECTOR_ANIMATION_MARKER_NAME_1);
index 43c7530..271246f 100644 (file)
@@ -429,8 +429,8 @@ int UtcDaliImageVisualWithFrameBufferPreMultipliedAlpha01(void)
   ToolkitTestApplication application;
   tet_infoline("Use FrameBuffer as url");
 
-  uint32_t width(64);
-  uint32_t height(64);
+  uint32_t    width(64);
+  uint32_t    height(64);
   FrameBuffer frameBuffer = Dali::FrameBuffer::New(width, height, FrameBuffer::Attachment::NONE);
 
   DALI_TEST_CHECK(frameBuffer);
@@ -474,8 +474,8 @@ int UtcDaliImageVisualWithFrameBufferPreMultipliedAlpha02(void)
   ToolkitTestApplication application;
   tet_infoline("Use FrameBuffer as url");
 
-  uint32_t width(64);
-  uint32_t height(64);
+  uint32_t    width(64);
+  uint32_t    height(64);
   FrameBuffer frameBuffer = Dali::FrameBuffer::New(width, height, FrameBuffer::Attachment::NONE);
 
   DALI_TEST_CHECK(frameBuffer);
@@ -616,6 +616,112 @@ int UtcDaliImageVisualWithPixelDataPreMultipliedAlpha(void)
   END_TEST;
 }
 
+int UtcDaliImageVisualWithPixelDataMasking(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("Load external texture with mask");
+
+  TestGlAbstraction& gl           = application.GetGlAbstraction();
+  TraceCallStack&    textureTrace = gl.GetTextureTrace();
+  textureTrace.Enable(true);
+
+  uint32_t width(64);
+  uint32_t height(64);
+  uint32_t bufferSize = width * height * Pixel::GetBytesPerPixel(Pixel::RGBA8888);
+
+  uint8_t*  buffer    = reinterpret_cast<uint8_t*>(malloc(bufferSize));
+  PixelData pixelData = PixelData::New(buffer, bufferSize, width, height, Pixel::RGBA8888, PixelData::FREE);
+
+  DALI_TEST_CHECK(pixelData);
+
+  ImageUrl    imageUrl = Dali::Toolkit::Image::GenerateUrl(pixelData, true);
+  std::string url      = imageUrl.GetUrl();
+
+  VisualFactory factory = VisualFactory::Get();
+  DALI_TEST_CHECK(factory);
+
+  Property::Map propertyMap;
+  propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::IMAGE);
+  propertyMap.Insert(ImageVisual::Property::URL, url);
+  propertyMap.Insert(ImageVisual::Property::ALPHA_MASK_URL, TEST_MASK_IMAGE_FILE_NAME);
+
+  Visual::Base visual = factory.CreateVisual(propertyMap);
+  DALI_TEST_CHECK(visual);
+
+  Property::Map testMap;
+  visual.CreatePropertyMap(testMap);
+  DALI_TEST_EQUALS(*testMap.Find(ImageVisual::Property::ALPHA_MASK_URL), Property::Value(TEST_MASK_IMAGE_FILE_NAME), TEST_LOCATION);
+
+  DummyControl      actor     = DummyControl::New();
+  DummyControlImpl& dummyImpl = static_cast<DummyControlImpl&>(actor.GetImplementation());
+  dummyImpl.RegisterVisual(Control::CONTROL_PROPERTY_END_INDEX + 1, visual);
+
+  DALI_TEST_EQUALS(actor.GetRendererCount(), 0u, TEST_LOCATION);
+
+  application.GetScene().Add(actor);
+  application.SendNotification();
+  application.Render(16);
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(actor.GetRendererCount(), 1u, TEST_LOCATION);
+  DALI_TEST_EQUALS(textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(actor.IsResourceReady(), true, TEST_LOCATION);
+
+  dummyImpl.UnregisterVisual(Control::CONTROL_PROPERTY_END_INDEX + 1);
+  DALI_TEST_EQUALS(actor.GetRendererCount(), 0u, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliImageVisualWithPixelDataMaskingSynchronously(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("Load synchronously external texture with mask");
+
+  uint32_t width(64);
+  uint32_t height(64);
+  uint32_t bufferSize = width * height * Pixel::GetBytesPerPixel(Pixel::RGBA8888);
+
+  uint8_t*  buffer    = reinterpret_cast<uint8_t*>(malloc(bufferSize));
+  PixelData pixelData = PixelData::New(buffer, bufferSize, width, height, Pixel::RGBA8888, PixelData::FREE);
+
+  DALI_TEST_CHECK(pixelData);
+
+  ImageUrl    imageUrl = Dali::Toolkit::Image::GenerateUrl(pixelData, true);
+  std::string url      = imageUrl.GetUrl();
+
+  VisualFactory factory = VisualFactory::Get();
+  DALI_TEST_CHECK(factory);
+
+  Property::Map propertyMap;
+  propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::IMAGE);
+  propertyMap.Insert(ImageVisual::Property::URL, url);
+  propertyMap.Insert(ImageVisual::Property::ALPHA_MASK_URL, TEST_MASK_IMAGE_FILE_NAME);
+  propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, true);
+
+  Visual::Base visual = factory.CreateVisual(propertyMap);
+  DALI_TEST_CHECK(visual);
+
+  Property::Map testMap;
+  visual.CreatePropertyMap(testMap);
+  DALI_TEST_EQUALS(*testMap.Find(ImageVisual::Property::ALPHA_MASK_URL), Property::Value(TEST_MASK_IMAGE_FILE_NAME), TEST_LOCATION);
+
+  DummyControl      actor     = DummyControl::New();
+  DummyControlImpl& dummyImpl = static_cast<DummyControlImpl&>(actor.GetImplementation());
+  dummyImpl.RegisterVisual(Control::CONTROL_PROPERTY_END_INDEX + 1, visual);
+
+  DALI_TEST_EQUALS(actor.GetRendererCount(), 0u, TEST_LOCATION);
+
+  application.GetScene().Add(actor);
+
+  DALI_TEST_EQUALS(actor.GetRendererCount(), 1u, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render(16);
+
+  END_TEST;
+}
+
 int UtcDaliImageVisualWithNativeImage(void)
 {
   ToolkitTestApplication application;
diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ParticleSystem.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ParticleSystem.cpp
new file mode 100644 (file)
index 0000000..7ef0b0e
--- /dev/null
@@ -0,0 +1,886 @@
+/*
+ * 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.
+ *
+ */
+
+#include <dali-toolkit/public-api/particle-system/particle-emitter.h>
+#include <dali-toolkit/public-api/particle-system/particle-domain.h>
+#include <dali-toolkit/public-api/particle-system/particle-modifier.h>
+#include <dali-toolkit/public-api/particle-system/particle-source.h>
+#include <dali-toolkit/public-api/particle-system/particle-renderer.h>
+#include <dali-toolkit/public-api/particle-system/particle-list.h>
+#include <dali-toolkit/public-api/particle-system/particle.h>
+#include <dali-test-suite-utils.h>
+
+#include <dlfcn.h>
+
+#include <future>
+
+using namespace Dali;
+using namespace Dali::Toolkit::ParticleSystem;
+
+/**
+ * Helper function to invoke next function in the call chain
+ */
+template<class R, class T, class... Args>
+R InvokeNext(T* pObj, Args... args)
+{
+  Dl_info info;
+  dladdr(__builtin_return_address(0), &info);
+  using Func = R(*)(T*,Args...);
+  auto sym = (Func)(dlsym(RTLD_NEXT, info.dli_sname));
+  return sym(pObj, args...);
+}
+
+// Create fake time getter
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+struct ParticleEmitter
+{
+  [[nodiscard]] std::chrono::milliseconds GetCurrentTimeMillis() const;
+
+  static std::chrono::milliseconds currentTime;
+
+  static void AdvanceTimeByMs( uint32_t ms)
+  {
+    currentTime += std::chrono::milliseconds(ms);
+  }
+};
+
+std::chrono::milliseconds ParticleEmitter::currentTime(1u);
+
+std::chrono::milliseconds ParticleEmitter::GetCurrentTimeMillis() const
+{
+  [[maybe_unused]] auto value = InvokeNext<std::chrono::milliseconds>(this);
+   return std::chrono::milliseconds(currentTime);
+}
+}
+
+using ParticleEmitterWrapper = Dali::Toolkit::ParticleSystem::Internal::ParticleEmitter;
+
+Texture CreateTexture()
+{
+  Texture texture = Texture::New(Dali::TextureType::TEXTURE_2D, Dali::Pixel::RGBA8888, 100, 100);
+  uint8_t* data = reinterpret_cast<uint8_t*>(malloc(100*100*4));
+  PixelData pixelData = PixelData::New(data, 100*100*4, 100, 100, Pixel::Format::RGBA8888, PixelData::FREE);
+  texture.Upload(pixelData);
+  return texture;
+}
+
+
+/**
+ * Test particle source
+ */
+class TestSource : public ParticleSourceInterface
+{
+public:
+
+  TestSource(ParticleEmitter* emitter)
+  {
+    mEmitter = *emitter;
+  }
+
+  void NewFrame()
+  {
+    mPromise = std::promise<uint32_t>();
+    mFuture = mPromise.get_future();
+  }
+
+  uint32_t Update(ParticleList& outList, uint32_t count) override
+  {
+    mPromise.set_value(count);
+    auto i = count;
+    while(--i)
+    {
+      outList.NewParticle(1.0f);
+    }
+    return count;
+  }
+
+  void Init() override
+  {
+    // calls initialized
+    mInitialized = true;
+  }
+
+  bool mInitialized{false};
+  std::future<uint32_t> mFuture;
+  std::promise<uint32_t> mPromise;
+  ParticleEmitter mEmitter;
+};
+
+class TestSource2 : public ParticleSourceInterface
+{
+public:
+
+  TestSource2(ParticleEmitter* emitter)
+  {
+    mEmitter = *emitter;
+  }
+
+  void NewFrame()
+  {
+    mPromise = std::promise<uint32_t>();
+    mFuture = mPromise.get_future();
+  }
+
+  uint32_t Update(ParticleList& outList, uint32_t count) override
+  {
+    mPromise.set_value(count);
+
+    while(count--)
+    {
+      auto particle = outList.NewParticle(1.0);
+      if(!particle)
+      {
+        return 0u;
+      }
+
+      [[maybe_unused]] auto& pos = particle.GetByIndex<Vector3>(mStreamBasePos);
+
+      [[maybe_unused]] auto& gpos = particle.Get<Vector3>(ParticleStream::POSITION_STREAM_BIT);
+      [[maybe_unused]] auto& col = particle.Get<Vector4>(ParticleStream::COLOR_STREAM_BIT);
+      [[maybe_unused]] auto& vel = particle.Get<Vector3>(ParticleStream::VELOCITY_STREAM_BIT);
+      [[maybe_unused]] auto& sca = particle.Get<Vector3>(ParticleStream::SCALE_STREAM_BIT);
+      //auto& basePos = particle.Get<Vector3>(ParticleStream::SCALE_STREAM_BIT);
+    }
+
+    return count;
+  }
+
+  void Init() override
+  {
+    // calls initialized
+    mStreamBasePos = mEmitter.GetParticleList().AddLocalStream<Vector3>(Vector3::ZERO);
+    mInitialized = true;
+  }
+
+  bool mInitialized{false};
+  std::future<uint32_t> mFuture;
+  std::promise<uint32_t> mPromise;
+  uint32_t mStreamBasePos{0u};
+  ParticleEmitter mEmitter;
+
+};
+/**
+ * Sample of FlameModifier
+ */
+struct TestModifier : public ParticleModifierInterface
+{
+  void Update(ParticleList& particleList, uint32_t firstParticleIndex, uint32_t particleCount) override
+  {
+
+  }
+};
+
+struct TestModifierMT : public ParticleModifierInterface
+{
+  void Update(ParticleList& particleList, uint32_t firstParticleIndex, uint32_t particleCount) override
+  {
+
+  }
+
+  bool IsMultiThreaded() override
+  {
+    return true;
+  }
+};
+
+/**
+ * Another modifier to test modifier stack
+ */
+struct TestModifier2 : public ParticleModifierInterface
+{
+  void Update(ParticleList& particleList, uint32_t firstParticleIndex, uint32_t particleCount) override
+  {
+
+  }
+};
+
+struct EmitterGroup
+{
+  ParticleEmitter emitter;
+  ParticleRenderer renderer;
+  ParticleModifier modifier;
+  ParticleSource source;
+};
+
+// Helper function to create emitter (every test will be doing that)
+template<class SOURCE, class MODIFIER>
+ParticleEmitter CreateEmitter(EmitterGroup* output = nullptr)
+{
+  auto emitter = ParticleEmitter::New();
+
+  bool result = (emitter != nullptr);
+  DALI_TEST_EQUALS( result, true, TEST_LOCATION );
+
+  // Create test source
+  auto source = ParticleSource::New<SOURCE>(&emitter);
+
+  {
+    BaseHandle handle(source);
+    auto       newHandle = ParticleSource::DownCast(handle);
+    DALI_TEST_EQUALS(newHandle, source, TEST_LOCATION);
+  }
+
+  // Create test renderer
+  auto renderer = ParticleRenderer::New();
+
+  {
+    BaseHandle handle(renderer);
+    auto       newHandle = ParticleRenderer::DownCast(handle);
+    DALI_TEST_EQUALS(newHandle, renderer, TEST_LOCATION);
+  }
+
+  // Create modifier
+  auto modifier = ParticleModifier::New<MODIFIER>();
+
+  {
+    BaseHandle handle(modifier);
+    auto       newHandle = ParticleModifier::DownCast(handle);
+    DALI_TEST_EQUALS(newHandle, modifier, TEST_LOCATION);
+  }
+
+  auto domain = ParticleDomain::New();
+
+  {
+    BaseHandle handle(domain);
+    auto       newHandle = ParticleDomain::DownCast(handle);
+    DALI_TEST_EQUALS(newHandle, domain, TEST_LOCATION);
+  }
+
+  // Test emitter readiness
+  auto ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::INCOMPLETE, TEST_LOCATION);
+
+  // Attach all components to the emitter
+  emitter.SetSource( source );
+  emitter.SetRenderer( renderer );
+  emitter.AddModifier( modifier );
+  emitter.SetDomain( domain );
+
+  auto domain0 = emitter.GetDomain();
+  auto renderer0 = emitter.GetRenderer();
+
+  DALI_TEST_EQUALS( renderer0, renderer, TEST_LOCATION);
+  DALI_TEST_EQUALS( domain0, domain, TEST_LOCATION);
+
+  if(output)
+  {
+    output->emitter = emitter;
+    output->renderer = renderer;
+    output->modifier = modifier;
+    output->source = source;
+  }
+
+  return emitter;
+}
+
+int UtcDaliParticleSystemEmitterNew(void)
+{
+  // create particle emitter
+  auto emitter = ParticleEmitter::New();
+
+  bool result = (emitter != nullptr);
+  DALI_TEST_EQUALS( result, true, TEST_LOCATION );
+
+  // Create test source
+  auto source = ParticleSource::New<TestSource>(&emitter);
+
+  // Create test renderer
+  auto renderer = ParticleRenderer::New();
+
+  // Create modifier
+  auto modifier = ParticleModifier::New<TestModifier>();
+
+  // Create domain
+  auto domain = ParticleDomain::New();
+
+  // Test emitter readiness
+  auto ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::INCOMPLETE, TEST_LOCATION);
+
+  // Attach all components to the emitter
+  emitter.SetSource( source );
+  emitter.SetRenderer( renderer );
+  emitter.AddModifier( modifier );
+  emitter.SetDomain( domain );
+
+  // test status again (domain is optional);
+  ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliParticleSystemEmitterModifierStack(void)
+{
+  // create particle emitter
+  auto emitter = ParticleEmitter::New();
+
+  bool result = (emitter != nullptr);
+  DALI_TEST_EQUALS( result, true, TEST_LOCATION );
+
+  // Create test source
+  auto source = ParticleSource::New<TestSource>(&emitter);
+
+  // Create test renderer
+  auto renderer = ParticleRenderer::New();
+
+  // Create modifier
+  auto modifier0 = ParticleModifier::New<TestModifier>();
+  auto modifier1 = ParticleModifier::New<TestModifier>();
+  auto modifier2 = ParticleModifier::New<TestModifier>();
+
+  // Create domain
+  auto domain = ParticleDomain::New();
+
+  // Test emitter readiness
+  auto ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::INCOMPLETE, TEST_LOCATION);
+
+  // Attach all components to the emitter
+  emitter.SetSource( source );
+  emitter.SetRenderer( renderer );
+  emitter.AddModifier( modifier0 );
+  emitter.AddModifier( modifier1 );
+  emitter.AddModifier( modifier2 );
+
+  emitter.SetDomain( domain );
+
+  // test status again (domain is optional);
+  ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+  auto modifier = emitter.GetModifierAt(1);
+  DALI_TEST_EQUALS(modifier, modifier1, TEST_LOCATION);
+
+  emitter.RemoveModifierAt(0);
+  modifier = emitter.GetModifierAt(0);
+  DALI_TEST_EQUALS(modifier, modifier1, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliParticleSystemTest(void)
+{
+  TestApplication application;
+
+  // Create actor to be used with emitter
+  Actor actor = Actor::New();
+  application.GetScene().Add(actor);
+  actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+  auto emitter = CreateEmitter<TestSource, TestModifier>();
+
+  // test status again (domain is optional);
+  auto ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+  // Set initial parameters of system
+  emitter.SetInitialParticleCount( 1000 );
+  emitter.SetActiveParticlesLimit( 5000 );
+
+  // Test getters
+  auto initialParticleCount = emitter.GetInitialParticleCount();
+  auto activeParticlesLimit = emitter.GetActiveParticlesLimit();
+
+  DALI_TEST_EQUALS(initialParticleCount, 1000, TEST_LOCATION);
+  DALI_TEST_EQUALS(activeParticlesLimit, 5000, TEST_LOCATION);
+
+  // Attach emitter to actor
+  emitter.AttachTo(actor);
+
+  // Start emitter
+  emitter.Start();
+
+  auto status = emitter.GetStatus();
+  DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+  auto& sourceCallback = dynamic_cast<TestSource&>(emitter.GetSource().GetSourceCallback());
+
+  // Run simulation
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  // First call into source callback should emit initial number of particles
+  auto emittedParticleCount = sourceCallback.mFuture.get();
+  DALI_TEST_EQUALS(emittedParticleCount, 1000, TEST_LOCATION);
+
+  // Run 3 more frames advancing by 1000ms which should
+  // emit particles based on emission rate
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  END_TEST;
+}
+
+int UtcDaliParticleSystemTestWithTextureScreen(void)
+{
+  TestApplication application;
+
+  // Create actor to be used with emitter
+  Actor actor = Actor::New();
+  application.GetScene().Add(actor);
+  actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+  EmitterGroup group;
+
+  auto emitter = CreateEmitter<TestSource, TestModifier>(&group);
+
+  // Blending mode with screen
+  auto texture = CreateTexture();
+  group.renderer.SetTexture( texture );
+  group.renderer.SetBlendingMode( BlendingMode::SCREEN );
+
+  // test status again (domain is optional);
+  auto ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+  // Set initial parameters of system
+  emitter.SetInitialParticleCount( 1000 );
+  emitter.SetActiveParticlesLimit( 5000 );
+
+  // Test getters
+  auto initialParticleCount = emitter.GetInitialParticleCount();
+  auto activeParticlesLimit = emitter.GetActiveParticlesLimit();
+
+  DALI_TEST_EQUALS(initialParticleCount, 1000, TEST_LOCATION);
+  DALI_TEST_EQUALS(activeParticlesLimit, 5000, TEST_LOCATION);
+
+  // Attach emitter to actor
+  emitter.AttachTo(actor);
+
+  // Start emitter
+  emitter.Start();
+
+  auto status = emitter.GetStatus();
+  DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+  auto& sourceCallback = dynamic_cast<TestSource&>(emitter.GetSource().GetSourceCallback());
+
+  // Run simulation
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  // First call into source callback should emit initial number of particles
+  auto emittedParticleCount = sourceCallback.mFuture.get();
+  DALI_TEST_EQUALS(emittedParticleCount, 1000, TEST_LOCATION);
+
+  // Run 3 more frames advancing by 1000ms which should
+  // emit particles based on emission rate
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  END_TEST;
+}
+
+int UtcDaliParticleSystemTestWithTextureAdd(void)
+{
+  TestApplication application;
+
+  // Create actor to be used with emitter
+  Actor actor = Actor::New();
+  application.GetScene().Add(actor);
+  actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+  EmitterGroup group;
+
+  auto emitter = CreateEmitter<TestSource, TestModifier>(&group);
+
+  // Blending mode with screen
+  auto texture = CreateTexture();
+  group.renderer.SetTexture( texture );
+  group.renderer.SetBlendingMode( BlendingMode::DEFAULT );
+
+  // test status again (domain is optional);
+  auto ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+  // Set initial parameters of system
+  emitter.SetInitialParticleCount( 1000 );
+  emitter.SetActiveParticlesLimit( 5000 );
+
+  // Test getters
+  auto initialParticleCount = emitter.GetInitialParticleCount();
+  auto activeParticlesLimit = emitter.GetActiveParticlesLimit();
+
+  DALI_TEST_EQUALS(initialParticleCount, 1000, TEST_LOCATION);
+  DALI_TEST_EQUALS(activeParticlesLimit, 5000, TEST_LOCATION);
+
+  // Attach emitter to actor
+  emitter.AttachTo(actor);
+
+  // Start emitter
+  emitter.Start();
+
+  auto status = emitter.GetStatus();
+  DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+  auto& sourceCallback = dynamic_cast<TestSource&>(emitter.GetSource().GetSourceCallback());
+
+  // Run simulation
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  // First call into source callback should emit initial number of particles
+  auto emittedParticleCount = sourceCallback.mFuture.get();
+  DALI_TEST_EQUALS(emittedParticleCount, 1000, TEST_LOCATION);
+
+  // Run 3 more frames advancing by 1000ms which should
+  // emit particles based on emission rate
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  END_TEST;
+}
+
+int UtcDaliParticleSystemTestInitialSetup(void)
+{
+  TestApplication application;
+
+  // Create actor to be used with emitter
+  Actor actor = Actor::New();
+  application.GetScene().Add(actor);
+  actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+  EmitterGroup group;
+
+  auto emitter = CreateEmitter<TestSource, TestModifier>(&group);
+
+  emitter.SetEmissionRate( 1000 );
+  emitter.SetInitialParticleCount( 1000 );
+  emitter.SetActiveParticlesLimit( 10000 );
+
+  auto emissionRate = emitter.GetEmissionRate();
+  auto initialCount = emitter.GetInitialParticleCount();
+  auto activeCount = emitter.GetActiveParticlesLimit();
+
+  DALI_TEST_EQUALS( emissionRate, 1000, TEST_LOCATION);
+  DALI_TEST_EQUALS( initialCount, 1000, TEST_LOCATION);
+  DALI_TEST_EQUALS( activeCount, 10000, TEST_LOCATION);
+
+  // Blending mode with screen
+  auto texture = CreateTexture();
+  group.renderer.SetTexture( texture );
+  group.renderer.SetBlendingMode( BlendingMode::DEFAULT );
+
+  // test status again (domain is optional);
+  auto ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+  // Set initial parameters of system
+  emitter.SetInitialParticleCount( 1000 );
+  emitter.SetActiveParticlesLimit( 5000 );
+
+  // Test getters
+  auto initialParticleCount = emitter.GetInitialParticleCount();
+  auto activeParticlesLimit = emitter.GetActiveParticlesLimit();
+
+  DALI_TEST_EQUALS(initialParticleCount, 1000, TEST_LOCATION);
+  DALI_TEST_EQUALS(activeParticlesLimit, 5000, TEST_LOCATION);
+
+  // Attach emitter to actor
+  emitter.AttachTo(actor);
+
+  // Start emitter
+  emitter.Start();
+
+  auto status = emitter.GetStatus();
+  DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+  auto& sourceCallback = dynamic_cast<TestSource&>(emitter.GetSource().GetSourceCallback());
+
+  // Run simulation
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  // First call into source callback should emit initial number of particles
+  auto emittedParticleCount = sourceCallback.mFuture.get();
+  DALI_TEST_EQUALS(emittedParticleCount, 1000, TEST_LOCATION);
+
+  // Run 3 more frames advancing by 1000ms which should
+  // emit particles based on emission rate
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  END_TEST;
+}
+
+int UtcDaliParticleSystemTestMT(void)
+{
+  TestApplication application;
+
+  // Create actor to be used with emitter
+  Actor actor = Actor::New();
+  application.GetScene().Add(actor);
+  actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+  EmitterGroup group;
+
+  auto emitter = CreateEmitter<TestSource, TestModifierMT>(&group);
+
+  emitter.SetEmissionRate( 10000 );
+  emitter.SetInitialParticleCount( 10000 );
+  emitter.SetActiveParticlesLimit( 20000 );
+  emitter.SetParticleCount( 300000 );
+
+  auto emissionRate = emitter.GetEmissionRate();
+  auto initialCount = emitter.GetInitialParticleCount();
+  auto activeCount = emitter.GetActiveParticlesLimit();
+
+  DALI_TEST_EQUALS( emissionRate, 10000, TEST_LOCATION);
+  DALI_TEST_EQUALS( initialCount, 10000, TEST_LOCATION);
+  DALI_TEST_EQUALS( activeCount, 20000, TEST_LOCATION);
+
+  emitter.EnableParallelProcessing(true);
+
+  auto mtEnabled = emitter.IsParallelProcessingEnabled();
+  DALI_TEST_EQUALS(mtEnabled, true, TEST_LOCATION);
+
+  // Blending mode with screen
+  auto texture = CreateTexture();
+  group.renderer.SetTexture( texture );
+  group.renderer.SetBlendingMode( BlendingMode::DEFAULT );
+  
+  // test status again (domain is optional);
+  auto ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+  // Attach emitter to actor
+  emitter.AttachTo(actor);
+
+  // Start emitter
+  emitter.Start();
+
+  auto status = emitter.GetStatus();
+  DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+  auto& sourceCallback = dynamic_cast<TestSource&>(emitter.GetSource().GetSourceCallback());
+
+  // Run simulation
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  // First call into source callback should emit initial number of particles
+  auto emittedParticleCount = sourceCallback.mFuture.get();
+  DALI_TEST_EQUALS(emittedParticleCount, 10000, TEST_LOCATION);
+
+  // Run 3 more frames advancing by 1000ms which should
+  // emit particles based on emission rate
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  END_TEST;
+}
+
+int UtcDaliParticleSystemTestParticleSource(void)
+{
+  TestApplication application;
+
+  // Create actor to be used with emitter
+  Actor actor = Actor::New();
+  application.GetScene().Add(actor);
+  actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+  EmitterGroup group;
+
+  auto emitter = CreateEmitter<TestSource2, TestModifier>(&group);
+
+  emitter.SetEmissionRate( 1000 );
+  emitter.SetInitialParticleCount( 1000 );
+  emitter.SetActiveParticlesLimit( 10000 );
+
+  auto emissionRate = emitter.GetEmissionRate();
+  auto initialCount = emitter.GetInitialParticleCount();
+  auto activeCount = emitter.GetActiveParticlesLimit();
+
+  DALI_TEST_EQUALS( emissionRate, 1000, TEST_LOCATION);
+  DALI_TEST_EQUALS( initialCount, 1000, TEST_LOCATION);
+  DALI_TEST_EQUALS( activeCount, 10000, TEST_LOCATION);
+
+  emitter.EnableParallelProcessing(true);
+
+  // Blending mode with screen
+  auto texture = CreateTexture();
+  group.renderer.SetTexture( texture );
+  group.renderer.SetBlendingMode( BlendingMode::DEFAULT );
+
+  // test status again (domain is optional);
+  auto ready = emitter.GetStatus();
+
+  // Emitter should return status incomplete
+  DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+  // Set initial parameters of system
+  emitter.SetInitialParticleCount( 1000 );
+  emitter.SetActiveParticlesLimit( 5000 );
+
+  // Test getters
+  auto initialParticleCount = emitter.GetInitialParticleCount();
+  auto activeParticlesLimit = emitter.GetActiveParticlesLimit();
+
+  DALI_TEST_EQUALS(initialParticleCount, 1000, TEST_LOCATION);
+  DALI_TEST_EQUALS(activeParticlesLimit, 5000, TEST_LOCATION);
+
+  // Attach emitter to actor
+  emitter.AttachTo(actor);
+
+  // Start emitter
+  emitter.Start();
+
+  auto status = emitter.GetStatus();
+  DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+  auto& sourceCallback = static_cast<TestSource2&>(emitter.GetSource().GetSourceCallback());
+
+  // Run simulation
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  // First call into source callback should emit initial number of particles
+  auto emittedParticleCount = sourceCallback.mFuture.get();
+  DALI_TEST_EQUALS(emittedParticleCount, 1000, TEST_LOCATION);
+
+  // Run 3 more frames advancing by 1000ms which should
+  // emit particles based on emission rate
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  // Stop the emitter
+  emitter.Stop();
+
+  sourceCallback.NewFrame();
+  application.SendNotification();
+  application.Render();
+
+  END_TEST;
+}
\ No newline at end of file
index a1f4efd..ba91bb2 100644 (file)
@@ -325,9 +325,11 @@ ELSE()
           DESTINATION bin)
 ENDIF()
 
-SET(BUILT_IN_SHADER_GEN_CPP "${GENERATED_SHADER_DIR}/generated/builtin-shader-gen.cpp")
+SET(BUILT_IN_SHADER_GEN_CPP "${GENERATED_SHADER_DIR}/generated/builtin-shader-gen.cpp" )
+
+FILE(GLOB SHADERS_SRC "${SHADER_SOURCE_DIR}/*.vert" "${SHADER_SOURCE_DIR}/*.frag" )
 ADD_CUSTOM_COMMAND(OUTPUT ${BUILT_IN_SHADER_GEN_CPP}
-                   DEPENDS ${SHADER_GENERATOR_NAME}
+                   DEPENDS ${SHADER_GENERATOR_NAME} ${SHADERS_SRC}
                    COMMAND ${SHADER_GENERATOR_BINARY} ${SHADER_SOURCE_DIR} ${SHADER_GENERATED_DIR})
 
 SET(SOURCES ${SOURCES} ${BUILT_IN_SHADER_GEN_CPP})
index 08e646e..ec93469 100644 (file)
@@ -99,9 +99,10 @@ ELSE()
   SET(SHADER_GENERATOR_BINARY ${CMAKE_CURRENT_BINARY_DIR}/../${SHADER_GENERATOR_NAME})
 ENDIF()
 
+FILE(GLOB SHADERS_SRC "${SHADER_SOURCE_DIR}/*.vert" "${SHADER_SOURCE_DIR}/*.frag")
 SET( BUILT_IN_SHADER_GEN_CPP "${GENERATED_SHADER_DIR}/generated/builtin-shader-gen.cpp")
 ADD_CUSTOM_COMMAND(OUTPUT ${BUILT_IN_SHADER_GEN_CPP}
-                   DEPENDS ${SHADER_GENERATOR_BINARY}
+                   DEPENDS ${SHADER_GENERATOR_BINARY} ${SHADERS_SRC}
                    COMMAND ${SHADER_GENERATOR_BINARY} ${SHADER_SOURCE_DIR} ${SHADER_GENERATED_DIR})
 
 SET( scene3d_src_files ${scene3d_src_files} ${BUILT_IN_SHADER_GEN_CPP} )
index 2a8d9f4..61485fb 100644 (file)
@@ -111,7 +111,7 @@ NavigationMesh::NavigationMesh(const std::vector<uint8_t>& buffer)
   return mHeader.vertexCount;
 }
 
-bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, uint32_t faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition)
+bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, FaceIndex faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition)
 {
   if(faceIndex == ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE)
   {
@@ -130,7 +130,7 @@ bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, uint32_t fa
   // Ray direction matches gravity direction
   ray.direction = Vector3(mHeader.gravityVector);
 
-  IntersectResult result = NavigationRayFaceIntersection(ray, *GetFace(uint16_t(faceIndex)));
+  IntersectResult result = NavigationRayFaceIntersection(ray, *GetFace(faceIndex));
   if(result.result)
   {
     outPosition = PointLocalToScene(result.point);
@@ -144,35 +144,35 @@ bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, uint32_t fa
     }
 
     // Test neighbouring by edges
-    const auto& poly = GetFace(uint16_t(faceIndex));
+    const auto& poly = GetFace(faceIndex);
 
     // collect faces sharing edges
-    std::vector<uint32_t> faces;
+    std::vector<FaceIndex> neighbourFaces;
 
-    for(unsigned short i : poly->edge)
+    for(auto edgeIndex : poly->edge)
     {
-      const auto& edge = *GetEdge(i);
+      const auto& edge = *GetEdge(edgeIndex);
 
       // store faces
-      for(unsigned short j : edge.face)
+      for(auto edgeFaceIndex : edge.face)
       {
-        if(j != ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE && j != faceIndex)
+        if(edgeFaceIndex != ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE && edgeFaceIndex != faceIndex)
         {
-          faces.emplace_back(j);
+          neighbourFaces.emplace_back(edgeFaceIndex);
         }
       }
     }
 
-    if(faces.empty())
+    if(neighbourFaces.empty())
     {
       return false;
     }
 
-    for(const auto& p : faces)
+    for(const auto& neighbourFaceIndex : neighbourFaces)
     {
-      if(FindFloorForFace(position, p, true, outPosition))
+      if(FindFloorForFace(position, neighbourFaceIndex, true, outPosition))
       {
-        mCurrentFace = p;
+        mCurrentFace = neighbourFaceIndex;
         return true;
       }
     }
@@ -188,11 +188,11 @@ bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, uint32_t fa
  */
 bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition)
 {
-  uint32_t polyIndex = 0;
-  return FindFloor(position, outPosition, polyIndex);
+  FaceIndex faceIndex = 0u;
+  return FindFloor(position, outPosition, faceIndex);
 }
 
-bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, uint32_t& faceIndex)
+bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, FaceIndex& faceIndex)
 {
   [[maybe_unused]] auto newPos = PointSceneToLocal(Dali::Vector3(position));
 
@@ -205,12 +205,12 @@ bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& out
 
   const auto                   POLY_COUNT = GetFaceCount();
   std::vector<IntersectResult> results;
-  for(auto i = 0u; i < POLY_COUNT; ++i)
+  for(auto faceIndex = 0u; faceIndex < POLY_COUNT; ++faceIndex)
   {
-    auto result = NavigationRayFaceIntersection(ray, *GetFace(i));
+    auto result = NavigationRayFaceIntersection(ray, *GetFace(faceIndex));
     if(result.result)
     {
-      result.faceIndex = i;
+      result.faceIndex = faceIndex;
       results.emplace_back(result);
     }
   }
@@ -230,25 +230,25 @@ bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& out
   return true;
 }
 
-const Poly* NavigationMesh::GetFace(int index) const
+const Poly* NavigationMesh::GetFace(FaceIndex index) const
 {
   auto* basePtr = reinterpret_cast<const Poly*>(mBuffer.data() + mHeader.dataOffset + mHeader.polyDataOffset);
   return &basePtr[index];
 }
 
-const Edge* NavigationMesh::GetEdge(int index) const
+const Edge* NavigationMesh::GetEdge(EdgeIndex index) const
 {
   auto* basePtr = reinterpret_cast<const Edge*>(mBuffer.data() + mHeader.dataOffset + mHeader.edgeDataOffset);
   return &basePtr[index];
 }
 
-const Vertex* NavigationMesh::GetVertex(int index) const
+const Vertex* NavigationMesh::GetVertex(VertexIndex index) const
 {
   auto* basePtr = reinterpret_cast<const Vertex*>(mBuffer.data() + mHeader.dataOffset + mHeader.vertexDataOffset);
   return &basePtr[index];
 }
 
-NavigationMesh::IntersectResult NavigationMesh::NavigationRayFaceIntersection(NavigationRay& ray, const Face& face)
+NavigationMesh::IntersectResult NavigationMesh::NavigationRayFaceIntersection(NavigationRay& ray, const Face& face) const
 {
   auto vi0 = *GetVertex(face.vertex[0]);
   auto vi1 = *GetVertex(face.vertex[1]);
@@ -266,7 +266,7 @@ void NavigationMesh::SetTransform(const Dali::Matrix& transform)
   transform.InvertTransform(mTransformInverse);
 }
 
-Dali::Vector3 NavigationMesh::PointSceneToLocal(const Dali::Vector3& point)
+Dali::Vector3 NavigationMesh::PointSceneToLocal(const Dali::Vector3& point) const
 {
   // Transform point into navmesh space
   Dali::Vector4 invNewPos = mTransformInverse * Dali::Vector4(point.x, -point.y, point.z, 1.0f);
@@ -275,7 +275,7 @@ Dali::Vector3 NavigationMesh::PointSceneToLocal(const Dali::Vector3& point)
   return Dali::Vector3(invNewPos.AsFloat());
 }
 
-Dali::Vector3 NavigationMesh::PointLocalToScene(const Dali::Vector3& point)
+Dali::Vector3 NavigationMesh::PointLocalToScene(const Dali::Vector3& point) const
 {
   // Transform point into scene transform space
   Dali::Vector4 newPos = mTransform * Dali::Vector4(point.x, -point.y, point.z, 1.0f);
index f61a922..f1db5db 100644 (file)
@@ -42,6 +42,11 @@ namespace Dali::Scene3D::Internal::Algorithm
 {
 class NavigationRay;
 
+// Make each to change each index value's type here.
+using VertexIndex = Dali::Scene3D::Algorithm::VertexIndex;
+using EdgeIndex   = Dali::Scene3D::Algorithm::EdgeIndex;
+using FaceIndex   = Dali::Scene3D::Algorithm::FaceIndex;
+
 /**
  * @class NavigationMesh
  */
@@ -73,7 +78,7 @@ public:
   {
     Dali::Vector3 point;
     float         distance;
-    uint16_t      faceIndex;
+    FaceIndex     faceIndex;
     bool          result;
   };
 
@@ -93,15 +98,9 @@ public:
   [[nodiscard]] uint32_t GetVertexCount() const;
 
   /**
-   * Looks for floor only within the face
-   * @param[in] position Position to be projected onto the face
-   * @param[in] faceIndex Face index
-   * @param[in] dontCheckNeighbours states whether to traverse onto neighbouring faces
-   * @param[out] outPosition Output position
-   *
-   * @return true if success
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::FindFloorForFace()
    */
-  bool FindFloorForFace(const Dali::Vector3& position, uint32_t faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition);
+  bool FindFloorForFace(const Dali::Vector3& position, FaceIndex faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition);
 
   /**
    * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::FindFloor()
@@ -111,22 +110,22 @@ public:
   /**
    * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::FindFloor()
    */
-  bool FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, uint32_t& faceIndex);
+  bool FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, FaceIndex& faceIndex);
 
   /**
    * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetFace()
    */
-  [[nodiscard]] const Face* GetFace(int index) const;
+  [[nodiscard]] const Face* GetFace(FaceIndex index) const;
 
   /**
    * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetEdge()
    */
-  [[nodiscard]] const Edge* GetEdge(int index) const;
+  [[nodiscard]] const Edge* GetEdge(EdgeIndex index) const;
 
   /**
    * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetVertex()
    */
-  [[nodiscard]] const Vertex* GetVertex(int index) const;
+  [[nodiscard]] const Vertex* GetVertex(VertexIndex index) const;
 
   /**
    * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::SetSceneTransform()
@@ -136,17 +135,17 @@ public:
   /**
    * Tests intersection between navigation ray and face
    */
-  IntersectResult NavigationRayFaceIntersection(NavigationRay& ray, const Face& face);
+  IntersectResult NavigationRayFaceIntersection(NavigationRay& ray, const Face& face) const;
 
   /**
    * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::PointSceneToLocal()
    */
-  Dali::Vector3 PointSceneToLocal(const Dali::Vector3& point);
+  Dali::Vector3 PointSceneToLocal(const Dali::Vector3& point) const;
 
   /**
    * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::PointLocalToScene()
    */
-  Dali::Vector3 PointLocalToScene(const Dali::Vector3& point);
+  Dali::Vector3 PointLocalToScene(const Dali::Vector3& point) const;
 
   /**
    * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetGravityVector()
@@ -156,7 +155,7 @@ public:
 private:
   std::vector<uint8_t>     mBuffer;           //< Data buffer
   NavigationMeshHeader_V10 mHeader;           //< Navigation mesh header
-  uint16_t                 mCurrentFace;      //< Current face (last floor position)
+  FaceIndex                mCurrentFace;      //< Current face (last floor position)
   Dali::Matrix             mTransform;        //< Transform matrix
   Dali::Matrix             mTransformInverse; //< Inverse of the transform matrix
 };
index 186acf2..726c1fb 100644 (file)
@@ -40,7 +40,7 @@ PathFinderAlgorithmDjikstra::~PathFinderAlgorithmDjikstra() = default;
 Scene3D::Algorithm::WayPointList PathFinderAlgorithmDjikstra::FindPath(const Dali::Vector3& positionFrom, const Dali::Vector3& positionTo)
 {
   Dali::Vector3 outPosFrom;
-  uint32_t      polyIndexFrom;
+  FaceIndex     polyIndexFrom;
   auto          result = mNavigationMesh->FindFloor(positionFrom, outPosFrom, polyIndexFrom);
 
   Scene3D::Algorithm::WayPointList waypoints;
@@ -48,7 +48,7 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmDjikstra::FindPath(const Dal
   if(result)
   {
     Dali::Vector3 outPosTo;
-    uint32_t      polyIndexTo;
+    FaceIndex     polyIndexTo;
     result = mNavigationMesh->FindFloor(positionTo, outPosTo, polyIndexTo);
 
     if(result)
@@ -75,11 +75,11 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmDjikstra::FindPath(const Dal
   return waypoints;
 }
 
-Scene3D::Algorithm::WayPointList PathFinderAlgorithmDjikstra::FindPath(uint32_t sourcePolyIndex, uint32_t targetPolyIndex)
+Scene3D::Algorithm::WayPointList PathFinderAlgorithmDjikstra::FindPath(FaceIndex sourcePolyIndex, FaceIndex targetPolyIndex)
 {
-  auto                  nodeCount = uint32_t(mNodes.size());
-  std::vector<float>    dist;
-  std::vector<uint32_t> prev;
+  auto                   nodeCount = uint32_t(mNodes.size());
+  std::vector<float>     dist;
+  std::vector<FaceIndex> prev;
 
   dist.resize(mNodes.size());
   prev.resize(mNodes.size());
@@ -156,8 +156,8 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmDjikstra::FindPath(uint32_t
   } while(removeCount != nodeCount);
 
   // Compute distances for each node back to the source
-  auto                u = targetPolyIndex;
-  std::list<uint32_t> q;
+  auto                 u = targetPolyIndex;
+  std::list<FaceIndex> q;
   if(prev[u] != Scene3D::Algorithm::NavigationMesh::NULL_FACE || u == sourcePolyIndex)
   {
     while(u != Scene3D::Algorithm::NavigationMesh::NULL_FACE)
@@ -208,6 +208,7 @@ void PathFinderAlgorithmDjikstra::PrepareData()
   mNodes.resize(faceCount);
 
   // for each face build the list
+  // TODO : Currently, we are assume that FaceNodeIndex is matched with FaceIndex 1:1. This might be changed in future.
   for(auto i = 0u; i < faceCount; ++i)
   {
     auto&       node = mNodes[i];
index 21bd316..08a6cee 100644 (file)
@@ -54,11 +54,11 @@ public:
    * @param[in] polyIndexTo Index of end polygon
    * @return List of waypoints for path
    */
-  Scene3D::Algorithm::WayPointList FindPath(uint32_t sourcePolyIndex, uint32_t targetPolyIndex) override;
+  Scene3D::Algorithm::WayPointList FindPath(FaceIndex sourcePolyIndex, FaceIndex targetPolyIndex) override;
 
-  [[nodiscard]] inline const NavigationMesh::Face* Face(uint32_t i) const
+  [[nodiscard]] inline const NavigationMesh::Face* Face(FaceIndex faceIndex) const
   {
-    return mNavigationMesh->GetFace(i);
+    return mNavigationMesh->GetFace(faceIndex);
   }
 
   /**
@@ -74,16 +74,15 @@ public:
    */
   struct FaceNode
   {
-    uint32_t faceIndex; ///< Index of face which is associated with node
-
     // neighbours
-    uint32_t faces[3]; ///< List of neighbouring faces (max 3 for a triangle)
-    uint32_t edges[3]; ///< List of edges (max 3 for a triangle)
-    float    weight[3]; ///< List of weights (by distance) to each neighbour
+    FaceIndex faces[3];  ///< List of neighbouring faces (max 3 for a triangle)
+    EdgeIndex edges[3];  ///< List of edges (max 3 for a triangle)
+    float     weight[3]; ///< List of weights (by distance) to each neighbour
   };
+  using FaceNodeIndex = FaceIndex;
 
   NavigationMesh*       mNavigationMesh; ///< Pointer to a valid NavigationMesh
-  std::vector<FaceNode> mNodes; ///< List of nodes
+  std::vector<FaceNode> mNodes;          ///< List of nodes
 };
 } // namespace Dali::Scene3D::Internal::Algorithm
 #endif // DALI_SCENE3D_INTERNAL_PATH_FINDER_DJIKSTRA_H
index 3b5f844..0eb29a3 100644 (file)
@@ -26,7 +26,8 @@
 #include <dali-scene3d/internal/algorithm/path-finder-waypoint-data.h>
 #include <dali-scene3d/public-api/algorithm/path-finder-waypoint.h>
 
-using WayPointList = Dali::Scene3D::Algorithm::WayPointList;
+using WayPointList  = Dali::Scene3D::Algorithm::WayPointList;
+using FaceNodeIndex = Dali::Scene3D::Internal::Algorithm::PathFinderAlgorithmSPFADoubleWay::FaceNodeIndex;
 
 namespace
 {
@@ -38,16 +39,16 @@ constexpr float PRIORITY_SCALE_FACTOR = 0.7f; ///< The value of heuristic factor
  *
  * @param[in,out] components Container of components id stored.
  * @param[in] index index what we want to get components's id.
- * @return uint32_t top-value of this components.
+ * @return FaceIndex top-value of this components.
  */
-uint32_t GetComponentId(std::vector<uint32_t>& components, uint32_t index)
+FaceNodeIndex GetComponentId(std::vector<FaceNodeIndex>& components, FaceNodeIndex index)
 {
   if(components[index] == index)
   {
     return index;
   }
   // Get my parent's components id, and update myself.
-  uint32_t ret             = GetComponentId(components, components[index]);
+  FaceNodeIndex ret        = GetComponentId(components, components[index]);
   return components[index] = ret;
 }
 
@@ -59,25 +60,25 @@ uint32_t GetComponentId(std::vector<uint32_t>& components, uint32_t index)
  * @param[in] index0 index of components what we want to be combined.
  * @param[in] index1 index of components what we want to be combined.
  */
-void ComponentsCombine(std::vector<uint32_t>& components, std::vector<uint32_t>& componentsLevel, uint32_t index0, uint32_t index1)
+void ComponentsCombine(std::vector<FaceNodeIndex>& components, std::vector<FaceNodeIndex>& componentsLevel, FaceNodeIndex index0, FaceNodeIndex index1)
 {
-  uint32_t p0 = GetComponentId(components, index0);
-  uint32_t p1 = GetComponentId(components, index1);
-  if(p0 == p1)
+  FaceNodeIndex ancestor0 = GetComponentId(components, index0);
+  FaceNodeIndex ancestor1 = GetComponentId(components, index1);
+  if(ancestor0 == ancestor1)
   {
     return;
   }
 
-  if(componentsLevel[p0] < componentsLevel[p1])
+  if(componentsLevel[ancestor0] < componentsLevel[ancestor1])
   {
-    components[p0] = p1;
+    components[ancestor0] = ancestor1;
   }
   else
   {
-    components[p1] = p0;
-    if(componentsLevel[p0] == componentsLevel[p1])
+    components[ancestor1] = ancestor0;
+    if(componentsLevel[ancestor0] == componentsLevel[ancestor1])
     {
-      ++componentsLevel[p0];
+      ++componentsLevel[ancestor0];
     }
   }
 }
@@ -93,7 +94,7 @@ PathFinderAlgorithmSPFADoubleWay::PathFinderAlgorithmSPFADoubleWay(Dali::Scene3D
 
 PathFinderAlgorithmSPFADoubleWay::~PathFinderAlgorithmSPFADoubleWay() = default;
 
-float PathFinderAlgorithmSPFADoubleWay::DistancePanaltyCalculate(uint32_t index) const noexcept
+float PathFinderAlgorithmSPFADoubleWay::DistancePanaltyCalculate(FaceIndex index) const noexcept
 {
   return dist[index] - priority[index] * PRIORITY_SCALE_FACTOR;
 }
@@ -101,7 +102,7 @@ float PathFinderAlgorithmSPFADoubleWay::DistancePanaltyCalculate(uint32_t index)
 Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFADoubleWay::FindPath(const Dali::Vector3& positionFrom, const Dali::Vector3& positionTo)
 {
   Dali::Vector3 outPosFrom;
-  uint32_t      polyIndexFrom;
+  FaceIndex     polyIndexFrom;
   auto          result = mNavigationMesh->FindFloor(positionFrom, outPosFrom, polyIndexFrom);
 
   Scene3D::Algorithm::WayPointList waypoints;
@@ -109,7 +110,7 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFADoubleWay::FindPath(cons
   if(result)
   {
     Dali::Vector3 outPosTo;
-    uint32_t      polyIndexTo;
+    FaceIndex     polyIndexTo;
     result = mNavigationMesh->FindFloor(positionTo, outPosTo, polyIndexTo);
 
     if(result)
@@ -136,7 +137,7 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFADoubleWay::FindPath(cons
   return waypoints;
 }
 
-Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFADoubleWay::FindPath(uint32_t sourcePolyIndex, uint32_t targetPolyIndex)
+Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFADoubleWay::FindPath(FaceIndex sourcePolyIndex, FaceIndex targetPolyIndex)
 {
   // Fast return if source and target index is same.
   if(sourcePolyIndex == targetPolyIndex)
@@ -160,11 +161,11 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFADoubleWay::FindPath(uint
   }
 
   // pair<navimesh FaceIndex, is backward direction>
-  using queueItem = std::pair<uint32_t, uint8_t>;
+  using queueItem = std::pair<FaceIndex, uint8_t>;
 
   std::list<queueItem> nodeQueue;
 
-  std::unordered_set<uint32_t> usedPolyIndexs[2];
+  std::unordered_set<FaceIndex> usedPolyIndexs[2];
 
   // Set distance of source and target
   dist[sourcePolyIndex]     = 0.0f;
@@ -178,9 +179,9 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFADoubleWay::FindPath(uint
   usedPolyIndexs[0].insert(sourcePolyIndex);
   usedPolyIndexs[1].insert(targetPolyIndex);
 
-  bool     foundPath          = false;
-  uint32_t forwardEndIndex    = Scene3D::Algorithm::NavigationMesh::NULL_FACE;
-  uint32_t backwardStartIndex = Scene3D::Algorithm::NavigationMesh::NULL_FACE;
+  bool      foundPath          = false;
+  FaceIndex forwardEndIndex    = Scene3D::Algorithm::NavigationMesh::NULL_FACE;
+  FaceIndex backwardStartIndex = Scene3D::Algorithm::NavigationMesh::NULL_FACE;
 
   const auto sourcePos = Dali::Vector3(Face(sourcePolyIndex)->center);
   const auto targetPos = Dali::Vector3(Face(targetPolyIndex)->center);
@@ -265,9 +266,9 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFADoubleWay::FindPath(uint
   }
 
   // Build path of face index
-  std::list<uint32_t> q;
+  std::list<FaceIndex> q;
   {
-    uint32_t u = forwardEndIndex;
+    FaceIndex u = forwardEndIndex;
     while(u != Scene3D::Algorithm::NavigationMesh::NULL_FACE)
     {
       q.push_front(u);
@@ -275,7 +276,7 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFADoubleWay::FindPath(uint
     }
   }
   {
-    uint32_t u = backwardStartIndex;
+    FaceIndex u = backwardStartIndex;
     while(u != Scene3D::Algorithm::NavigationMesh::NULL_FACE)
     {
       q.push_back(u);
@@ -350,10 +351,10 @@ void PathFinderAlgorithmSPFADoubleWay::PrepareData()
   queued.resize(faceCount);
 
   // Temperal container for components level. It will be used for Union-Find algorithm.
-  std::vector<uint32_t> componentLevels(faceCount);
+  std::vector<FaceNodeIndex> componentLevels(faceCount);
 
   // Initialize path informations.
-  for(auto i = 0u; i < faceCount; ++i)
+  for(FaceNodeIndex i = 0u; i < faceCount; ++i)
   {
     dist[i]         = std::numeric_limits<float>::infinity();
     priority[i]     = -1.0f;                                         // Initialize by negative value, that we didn't calculate yet.
@@ -366,7 +367,8 @@ void PathFinderAlgorithmSPFADoubleWay::PrepareData()
   }
 
   // for each face build the list
-  for(auto i = 0u; i < faceCount; ++i)
+  // TODO : Currently, we are assume that FaceNodeIndex is matched with FaceIndex 1:1. This might be changed in future.
+  for(FaceNodeIndex i = 0u; i < faceCount; ++i)
   {
     auto&       node = mNodes[i];
     const auto* face = mNavigationMesh->GetFace(i);
index 32e04b9..60b7469 100644 (file)
@@ -54,11 +54,11 @@ public:
    * @param[in] polyIndexTo Index of end polygon
    * @return List of waypoints for path
    */
-  Scene3D::Algorithm::WayPointList FindPath(uint32_t sourcePolyIndex, uint32_t targetPolyIndex) override;
+  Scene3D::Algorithm::WayPointList FindPath(FaceIndex sourcePolyIndex, FaceIndex targetPolyIndex) override;
 
-  [[nodiscard]] inline const NavigationMesh::Face* Face(uint32_t i) const
+  [[nodiscard]] inline const NavigationMesh::Face* Face(FaceIndex faceIndex) const
   {
-    return mNavigationMesh->GetFace(i);
+    return mNavigationMesh->GetFace(faceIndex);
   }
 
   /**
@@ -75,31 +75,30 @@ public:
    *
    * @param[in] index index of node what we want to calculate panantly.
    */
-  float DistancePanaltyCalculate(uint32_t index) const noexcept;
+  float DistancePanaltyCalculate(FaceIndex index) const noexcept;
 
   /**
    * Structure describes single node of pathfinding algorithm
    */
   struct FaceNode
   {
-    uint32_t faceIndex; ///< Index of face which is associated with node
-
     // neighbours
-    uint32_t faces[3];  ///< List of neighbouring faces (max 3 for a triangle)
-    uint32_t edges[3];  ///< List of edges (max 3 for a triangle)
-    float    weight[3]; ///< List of weights (by distance) to each neighbour
+    FaceIndex faces[3];  ///< List of neighbouring faces (max 3 for a triangle)
+    EdgeIndex edges[3];  ///< List of edges (max 3 for a triangle)
+    float     weight[3]; ///< List of weights (by distance) to each neighbour
   };
+  using FaceNodeIndex = FaceIndex;
 
   NavigationMesh*       mNavigationMesh; ///< Pointer to a valid NavigationMesh
   std::vector<FaceNode> mNodes;          ///< List of nodes
 
 private:
-  std::vector<float>    dist;
-  std::vector<float>    priority; ///< Cached priority value. It will be calculated by source & target poly index per every queries.
-  std::vector<uint32_t> prevForward;
-  std::vector<uint32_t> prevBackward;
-  std::vector<uint32_t> componentIds; ///< Id of connected components per each nodes. It should be one of node's index.
-  std::vector<bool>     queued;
+  std::vector<float>         dist;
+  std::vector<float>         priority; ///< Cached priority value. It will be calculated by source & target poly index per every queries.
+  std::vector<FaceIndex>     prevForward;
+  std::vector<FaceIndex>     prevBackward;
+  std::vector<FaceNodeIndex> componentIds; ///< Id of connected components per each nodes. It should be one of node's index.
+  std::vector<bool>          queued;
 };
 } // namespace Dali::Scene3D::Internal::Algorithm
 #endif // DALI_SCENE3D_INTERNAL_PATH_FINDER_SPFA_DOUBLE_WAY_H
index aa8b3bd..c6b1d48 100644 (file)
@@ -40,7 +40,7 @@ PathFinderAlgorithmSPFA::~PathFinderAlgorithmSPFA() = default;
 Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFA::FindPath(const Dali::Vector3& positionFrom, const Dali::Vector3& positionTo)
 {
   Dali::Vector3 outPosFrom;
-  uint32_t      polyIndexFrom;
+  FaceIndex     polyIndexFrom;
   auto          result = mNavigationMesh->FindFloor(positionFrom, outPosFrom, polyIndexFrom);
 
   Scene3D::Algorithm::WayPointList waypoints;
@@ -48,7 +48,7 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFA::FindPath(const Dali::V
   if(result)
   {
     Dali::Vector3 outPosTo;
-    uint32_t      polyIndexTo;
+    FaceIndex     polyIndexTo;
     result = mNavigationMesh->FindFloor(positionTo, outPosTo, polyIndexTo);
 
     if(result)
@@ -75,18 +75,18 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFA::FindPath(const Dali::V
   return waypoints;
 }
 
-Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFA::FindPath(uint32_t sourcePolyIndex, uint32_t targetPolyIndex)
+Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFA::FindPath(FaceIndex sourcePolyIndex, FaceIndex targetPolyIndex)
 {
-  auto                  nodeCount = uint32_t(mNodes.size());
-  std::vector<float>    dist;
-  std::vector<uint32_t> prev;
-  std::vector<bool>     queued;
+  auto                   nodeCount = uint32_t(mNodes.size());
+  std::vector<float>     dist;
+  std::vector<FaceIndex> prev;
+  std::vector<bool>      queued;
 
   dist.resize(mNodes.size());
   prev.resize(mNodes.size());
   queued.resize(mNodes.size());
 
-  std::list<uint32_t> nodeQueue;
+  std::list<FaceIndex> nodeQueue;
 
   [[maybe_unused]] auto sourcePos = Dali::Vector3(Face(sourcePolyIndex)->center);
 
@@ -145,8 +145,8 @@ Scene3D::Algorithm::WayPointList PathFinderAlgorithmSPFA::FindPath(uint32_t sour
   }
 
   // Compute distances for each node back to the source
-  auto                u = targetPolyIndex;
-  std::list<uint32_t> q;
+  auto                 u = targetPolyIndex;
+  std::list<FaceIndex> q;
   if(prev[u] != Scene3D::Algorithm::NavigationMesh::NULL_FACE || u == sourcePolyIndex)
   {
     while(u != Scene3D::Algorithm::NavigationMesh::NULL_FACE)
@@ -203,7 +203,8 @@ void PathFinderAlgorithmSPFA::PrepareData()
   mNodes.resize(faceCount);
 
   // for each face build the list
-  for(auto i = 0u; i < faceCount; ++i)
+  // TODO : Currently, we are assume that FaceNodeIndex is matched with FaceIndex 1:1. This might be changed in future.
+  for(FaceNodeIndex i = 0u; i < faceCount; ++i)
   {
     auto&       node = mNodes[i];
     const auto* face = mNavigationMesh->GetFace(i);
index 99fc4af..8b62206 100644 (file)
@@ -54,11 +54,11 @@ public:
    * @param[in] polyIndexTo Index of end polygon
    * @return List of waypoints for path
    */
-  Scene3D::Algorithm::WayPointList FindPath(uint32_t sourcePolyIndex, uint32_t targetPolyIndex) override;
+  Scene3D::Algorithm::WayPointList FindPath(FaceIndex sourcePolyIndex, FaceIndex targetPolyIndex) override;
 
-  [[nodiscard]] inline const NavigationMesh::Face* Face(uint32_t i) const
+  [[nodiscard]] inline const NavigationMesh::Face* Face(FaceIndex faceIndex) const
   {
-    return mNavigationMesh->GetFace(i);
+    return mNavigationMesh->GetFace(faceIndex);
   }
 
   /**
@@ -74,13 +74,12 @@ public:
    */
   struct FaceNode
   {
-    uint32_t faceIndex; ///< Index of face which is associated with node
-
     // neighbours
-    uint32_t faces[3];  ///< List of neighbouring faces (max 3 for a triangle)
-    uint32_t edges[3];  ///< List of edges (max 3 for a triangle)
-    float    weight[3]; ///< List of weights (by distance) to each neighbour
+    FaceIndex faces[3];  ///< List of neighbouring faces (max 3 for a triangle)
+    EdgeIndex edges[3];  ///< List of edges (max 3 for a triangle)
+    float     weight[3]; ///< List of weights (by distance) to each neighbour
   };
+  using FaceNodeIndex = FaceIndex;
 
   NavigationMesh*       mNavigationMesh; ///< Pointer to a valid NavigationMesh
   std::vector<FaceNode> mNodes;          ///< List of nodes
index be1b792..b0f8294 100644 (file)
@@ -66,6 +66,7 @@ DALI_TYPE_REGISTRATION_BEGIN(Scene3D::Model, Toolkit::Control, Create);
 DALI_TYPE_REGISTRATION_END()
 
 static constexpr Vector3 Y_DIRECTION(1.0f, -1.0f, 1.0f);
+static constexpr float   SIZE_STEP_CONDITION = 0.1f;
 
 static constexpr bool DEFAULT_MODEL_CHILDREN_SENSITIVE = false;
 static constexpr bool DEFAULT_MODEL_CHILDREN_FOCUSABLE = false;
@@ -501,6 +502,9 @@ void Model::OnSceneConnection(int depth)
   }
 
   NotifyResourceReady();
+
+  mSizeNotification = Self().AddPropertyNotification(Actor::Property::SIZE, StepCondition(SIZE_STEP_CONDITION));
+  mSizeNotification.NotifySignal().Connect(this, &Model::OnSizeNotification);
   Control::OnSceneConnection(depth);
 }
 
@@ -512,9 +516,19 @@ void Model::OnSceneDisconnection()
     GetImpl(sceneView).UnregisterSceneItem(this);
     mParentSceneView.Reset();
   }
+
+  mSizeNotification.NotifySignal().Disconnect(this, &Model::OnSizeNotification);
+  Self().RemovePropertyNotification(mSizeNotification);
+  mSizeNotification.Reset();
+
   Control::OnSceneDisconnection();
 }
 
+void Model::OnSizeSet(const Vector3& size)
+{
+  ScaleModel(false);
+}
+
 Vector3 Model::GetNaturalSize()
 {
   if(!mModelRoot)
@@ -543,7 +557,7 @@ float Model::GetWidthForHeight(float height)
 void Model::OnRelayout(const Vector2& size, RelayoutContainer& container)
 {
   Control::OnRelayout(size, container);
-  ScaleModel();
+  ScaleModel(false);
 }
 
 bool Model::IsResourceReady() const
@@ -562,7 +576,7 @@ void Model::CreateModelRoot()
   Self().Add(mModelRoot);
 }
 
-void Model::ScaleModel()
+void Model::ScaleModel(bool useCurrentSize)
 {
   if(!mModelRoot)
   {
@@ -570,7 +584,7 @@ void Model::ScaleModel()
   }
 
   float   scale = 1.0f;
-  Vector3 size  = Self().GetProperty<Vector3>(Dali::Actor::Property::SIZE);
+  Vector3 size  = (useCurrentSize) ? Self().GetCurrentProperty<Vector3>(Dali::Actor::Property::SIZE) : Self().GetProperty<Vector3>(Dali::Actor::Property::SIZE);
   if(size.x > 0.0f && size.y > 0.0f)
   {
     scale = MAXFLOAT;
@@ -797,6 +811,11 @@ void Model::OnIblLoadComplete()
   NotifyResourceReady();
 }
 
+void Model::OnSizeNotification(Dali::PropertyNotification& source)
+{
+  ScaleModel(true);
+}
+
 void Model::ResetResourceTasks()
 {
   if(!Dali::Adaptor::IsAvailable())
@@ -862,7 +881,7 @@ void Model::CreateModel()
     Self().SetProperty(Dali::Actor::Property::SIZE, mNaturalSize);
   }
   FitModelPosition();
-  ScaleModel();
+  ScaleModel(false);
 }
 
 void Model::CreateAnimations(Dali::Scene3D::Loader::SceneDefinition& scene)
index 23aaf61..d7924bf 100644 (file)
@@ -23,6 +23,7 @@
 #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/property-notification.h>
 #include <dali/public-api/object/weak-handle.h>
 #include <dali/public-api/rendering/texture.h>
 
@@ -172,6 +173,11 @@ private:
   void OnSceneDisconnection() override;
 
   /**
+   * @copydoc CustomActorImpl::OnSizeSet( const Vector3& size )
+   */
+  void OnSizeSet(const Vector3& size) override;
+
+  /**
    * @copydoc Toolkit::Control::GetNaturalSize
    */
   Vector3 GetNaturalSize() override;
@@ -205,7 +211,7 @@ private:
   /**
    * @brief Scales the model to fit the control or to return to original size.
    */
-  void ScaleModel();
+  void ScaleModel(bool useCurrentSize);
 
   /**
    * @brief Changes model anchor point to set the model at center or returns to the original model pivot.
@@ -274,6 +280,11 @@ private:
   void OnIblLoadComplete();
 
   /**
+   * @brief Update model root scale when Model size property is updated.
+   */
+  void OnSizeNotification(Dali::PropertyNotification& source);
+
+  /**
    * @brief Reset Resource loading tasks.
    */
   void ResetResourceTasks();
@@ -304,12 +315,13 @@ private:
   void ResetCameraParameters();
 
 private:
-  std::string                                 mModelUrl;
-  std::string                                 mResourceDirectoryUrl;
-  Scene3D::ModelNode                          mModelRoot;
-  std::vector<AnimationData>                  mAnimations;
-  std::vector<CameraData>                     mCameraParameters;
-  WeakHandle<Scene3D::SceneView>              mParentSceneView;
+  std::string                    mModelUrl;
+  std::string                    mResourceDirectoryUrl;
+  Scene3D::ModelNode             mModelRoot;
+  std::vector<AnimationData>     mAnimations;
+  std::vector<CameraData>        mCameraParameters;
+  WeakHandle<Scene3D::SceneView> mParentSceneView;
+  Dali::PropertyNotification     mSizeNotification;
 
   // Asynchronous loading variable
   ModelLoadTaskPtr          mModelLoadTask;
index bfcc18b..e701162 100644 (file)
@@ -77,7 +77,7 @@ uniform sampler2D sbrdfLUT;
 uniform samplerCube sDiffuseEnvSampler;
 uniform samplerCube sSpecularEnvSampler;
 uniform float uIblIntensity;
-uniform vec3 uYDirection;
+uniform mediump vec3 uYDirection;
 uniform float uMaxLOD;
 
 // For Alpha Mode.
index 5b48af0..8ca91ea 100644 (file)
@@ -41,6 +41,7 @@ uniform mat4 uProjection;
   in vec4 aWeights;
   #define MAX_BONES 64
   uniform mat4 uBone[MAX_BONES];
+  uniform mediump vec3 uYDirection;
 #endif
 
 #ifdef MORPH
@@ -141,10 +142,8 @@ void main()
     uBone[int(aJoints.z)] * aWeights.z +
     uBone[int(aJoints.w)] * aWeights.w;
   position = bone * position;
-  normal = (bone * vec4(normal, 0.0)).xyz;
-  tangent = (bone * vec4(tangent, 0.0)).xyz;
-  normal = normalize(normal);
-  tangent = normalize(tangent);
+  normal = uYDirection * (bone * vec4(normal, 0.0)).xyz;
+  tangent = uYDirection * (bone * vec4(tangent, 0.0)).xyz;
 
   highp vec4 positionW = position;
 #else
@@ -154,6 +153,8 @@ void main()
 
   vPositionToCamera = transpose(mat3(uViewMatrix)) * -vec3(positionV.xyz / positionV.w);
 
+  normal = normalize(normal);
+  tangent = normalize(tangent);
   vec3 bitangent = cross(normal, tangent);
 #ifdef VEC4_TANGENT
   bitangent *= aTangent.w;
index 257cbd0..54db4bc 100644 (file)
@@ -319,31 +319,26 @@ void ModelNode::UpdateBoneMatrix(Scene3D::ModelPrimitive primitive)
       continue;
     }
 
-    Dali::Shader shader = renderer.GetShader();
-    if(!shader)
-    {
-      continue;
-    }
-
     if(boneData.constraint)
     {
       boneData.constraint.Remove();
       boneData.constraint.Reset();
     }
 
-    if(shader.GetPropertyIndex(boneData.propertyName) == Property::INVALID_INDEX)
+    auto propBoneXform = renderer.GetPropertyIndex(boneData.propertyName);
+    if(propBoneXform == Property::INVALID_INDEX)
     {
-      auto propBoneXform = shader.RegisterProperty(boneData.propertyName, Matrix{false});
+      propBoneXform = renderer.RegisterProperty(boneData.propertyName, Matrix{false});
+    }
 
-      Matrix inverseMatrix = boneData.inverseMatrix;
-      // Constrain bone matrix to joint transform.
-      boneData.constraint = Constraint::New<Matrix>(shader, propBoneXform, [inverseMatrix](Matrix& output, const PropertyInputContainer& inputs)
-                                                    { Matrix::Multiply(output, inverseMatrix, inputs[0]->GetMatrix()); });
+    Matrix inverseMatrix = boneData.inverseMatrix;
+    // Constrain bone matrix to joint transform.
+    boneData.constraint = Constraint::New<Matrix>(renderer, propBoneXform, [inverseMatrix](Matrix& output, const PropertyInputContainer& inputs)
+                                                  { Matrix::Multiply(output, inverseMatrix, inputs[0]->GetMatrix()); });
 
-      Actor joint = Self();
-      boneData.constraint.AddSource(Source{joint, Actor::Property::WORLD_MATRIX});
-      boneData.constraint.ApplyPost();
-    }
+    Actor joint = Self();
+    boneData.constraint.AddSource(Source{joint, Actor::Property::WORLD_MATRIX});
+    boneData.constraint.ApplyPost();
     break;
   }
 }
index 45225ae..9b594d1 100644 (file)
@@ -24,10 +24,9 @@ using Dali::Vector3;
 
 namespace Dali::Scene3D::Algorithm
 {
-
-NavigationMesh::NavigationMesh( NavigationMeshImpl* impl )
+NavigationMesh::NavigationMesh(NavigationMeshImpl* impl)
 {
-  mImpl.reset( impl );
+  mImpl.reset(impl);
 }
 
 NavigationMesh::~NavigationMesh() = default;
@@ -47,27 +46,27 @@ NavigationMesh::~NavigationMesh() = default;
   return mImpl->GetVertexCount();
 }
 
-bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, uint32_t& polyIndex)
+bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, FaceIndex& faceIndex)
 {
-  return mImpl->FindFloor(position, outPosition, polyIndex);
+  return mImpl->FindFloor(position, outPosition, faceIndex);
 }
 
-bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, uint32_t faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition)
+bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, FaceIndex faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition)
 {
   return mImpl->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
 }
 
-[[nodiscard]] const NavigationMesh::Face* NavigationMesh::GetFace(int index) const
+[[nodiscard]] const NavigationMesh::Face* NavigationMesh::GetFace(FaceIndex index) const
 {
   return mImpl->GetFace(index);
 }
 
-[[nodiscard]] const NavigationMesh::Edge* NavigationMesh::GetEdge(int index) const
+[[nodiscard]] const NavigationMesh::Edge* NavigationMesh::GetEdge(EdgeIndex index) const
 {
   return mImpl->GetEdge(index);
 }
 
-[[nodiscard]] const NavigationMesh::Vertex* NavigationMesh::GetVertex(int index) const
+[[nodiscard]] const NavigationMesh::Vertex* NavigationMesh::GetVertex(VertexIndex index) const
 {
   return mImpl->GetVertex(index);
 }
@@ -77,12 +76,12 @@ void NavigationMesh::SetSceneTransform(const Dali::Matrix& transform)
   mImpl->SetTransform(transform);
 }
 
-Dali::Vector3 NavigationMesh::PointSceneToLocal(const Dali::Vector3& point)
+Dali::Vector3 NavigationMesh::PointSceneToLocal(const Dali::Vector3& point) const
 {
   return mImpl->PointSceneToLocal(point);
 }
 
-Dali::Vector3 NavigationMesh::PointLocalToScene(const Dali::Vector3& point)
+Dali::Vector3 NavigationMesh::PointLocalToScene(const Dali::Vector3& point) const
 {
   return mImpl->PointLocalToScene(point);
 }
@@ -92,4 +91,4 @@ Dali::Vector3 NavigationMesh::GetGravityVector() const
   return mImpl->GetGravityVector();
 }
 
-}
\ No newline at end of file
+} // namespace Dali::Scene3D::Algorithm
\ No newline at end of file
index b2a0fd9..727f9fc 100644 (file)
@@ -25,6 +25,7 @@
 
 #include <cinttypes>
 #include <cstdio>
+#include <limits>
 #include <memory>
 
 // INTERNAL INCLUDES
@@ -50,6 +51,11 @@ namespace Dali::Scene3D::Algorithm
 // Using PImpling but not usual DALi handles as this object isn't supposed to be refcounted
 using NavigationMeshImpl = Dali::Scene3D::Internal::Algorithm::NavigationMesh;
 
+// Make each to change each index value's type here.
+using VertexIndex = uint16_t;
+using EdgeIndex   = uint16_t;
+using FaceIndex   = uint16_t;
+
 /**
  * @class NavigationMesh
  *
@@ -75,14 +81,14 @@ public:
   /**
    * @struct Face
    *
-   * Describes a single polygon
+   * Describes a single polygon's face
    */
   struct Face
   {
-    uint16_t vertex[NAVIGATION_MESH_MAX_VERTICES_PER_FACE]; ///< Vertices per face
-    uint16_t edge[NAVIGATION_MESH_MAX_EDGES_PER_FACE];      ///< Edges per face
-    float    normal[NAVIGATION_MESH_MAX_COMPONENTS_3D];     ///< Normal vector
-    float    center[NAVIGATION_MESH_MAX_COMPONENTS_3D];     ///< Barycentric coordinates
+    VertexIndex vertex[NAVIGATION_MESH_MAX_VERTICES_PER_FACE]; ///< Vertices per face
+    EdgeIndex   edge[NAVIGATION_MESH_MAX_EDGES_PER_FACE];      ///< Edges per face
+    float       normal[NAVIGATION_MESH_MAX_COMPONENTS_3D];     ///< Normal vector
+    float       center[NAVIGATION_MESH_MAX_COMPONENTS_3D];     ///< Barycentric coordinates
   };
 
   /**
@@ -92,8 +98,8 @@ public:
    */
   struct Edge
   {
-    uint16_t vertex[NAVIGATION_MESH_MAX_COMPONENTS_2D]; ///< Vertices making the edge
-    uint16_t face[NAVIGATION_MESH_MAX_COMPONENTS_2D];   ///< Faces on both sides of edge
+    VertexIndex vertex[NAVIGATION_MESH_MAX_COMPONENTS_2D]; ///< Vertices making the edge
+    FaceIndex   face[NAVIGATION_MESH_MAX_COMPONENTS_2D];   ///< Faces on both sides of edge
   };
 
   /**
@@ -106,7 +112,7 @@ public:
   {
     union
     {
-      float co[NAVIGATION_MESH_MAX_COMPONENTS_3D]; ///< Coordinates of vertex
+      float coordinates[NAVIGATION_MESH_MAX_COMPONENTS_3D]; ///< Coordinates of vertex
       struct
       {
         float x, y, z;
@@ -147,11 +153,11 @@ public:
    * @brief Looks for the floor under specified position
    * @param[in] position Position to investigate
    * @param[in] outPosition Position on the floor in found
-   * @param[in] faceIndex Index of NavigationMesh face associated with floor
+   * @param[out] faceIndex Index of NavigationMesh face associated with floor
    *
    * @return True if floor has been found, False otherwise
    */
-  bool FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, uint32_t& faceIndex);
+  bool FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, FaceIndex& faceIndex);
 
   /**
    * @brief Looks for a floor starting from specified face
@@ -167,28 +173,28 @@ public:
    *
    * @return True on success, false otherwise
    */
-  bool FindFloorForFace(const Dali::Vector3& position, uint32_t faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition);
+  bool FindFloorForFace(const Dali::Vector3& position, FaceIndex faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition);
 
   /**
    * @brief Returns pointer to Face structure
    * @param[in] index Index of face to retrieve
    * @return Pointer to valid Face structure or nullptr
    */
-  [[nodiscard]] const Face* GetFace(int index) const;
+  [[nodiscard]] const Face* GetFace(FaceIndex index) const;
 
   /**
    * @brief Returns edge structure
    * @param[in] index Index of edge to retrieve
    * @return Pointer to valid Edge structure or nullptr
    */
-  [[nodiscard]] const Edge* GetEdge(int index) const;
+  [[nodiscard]] const Edge* GetEdge(EdgeIndex index) const;
 
   /**
    * @brief Returns vertex structure
    * @param[in] index Index of vertex to retrieve
    * @return Pointer to valid Vertex structure or nullptr
    */
-  [[nodiscard]] const Vertex* GetVertex(int index) const;
+  [[nodiscard]] const Vertex* GetVertex(VertexIndex index) const;
 
   /**
    * @brief Sets static transform for the navigation mesh object
@@ -225,7 +231,7 @@ public:
    * @param[in] point Point to transform
    * @return Point transformed to the local space
    */
-  Dali::Vector3 PointSceneToLocal(const Dali::Vector3& point);
+  Dali::Vector3 PointSceneToLocal(const Dali::Vector3& point) const;
 
   /**
    * @brief Transforms point into the parent transform space
@@ -235,7 +241,7 @@ public:
    * @param[in] point Point to transform
    * @return Point transformed into the parent space
    */
-  Dali::Vector3 PointLocalToScene(const Dali::Vector3& point);
+  Dali::Vector3 PointLocalToScene(const Dali::Vector3& point) const;
 
   /**
    * @brief Returns direction of the gravity vector
@@ -246,8 +252,8 @@ public:
    */
   Dali::Vector3 GetGravityVector() const;
 
-  static constexpr uint16_t NULL_FACE{0xffff}; ///< Represents null polygon
-  static constexpr uint16_t NULL_EDGE{0xffff}; ///< represents null edge
+  static constexpr FaceIndex NULL_FACE{std::numeric_limits<FaceIndex>::max()}; ///< Represents null face
+  static constexpr EdgeIndex NULL_EDGE{std::numeric_limits<EdgeIndex>::max()}; ///< Represents null edge
 
 public:
   DALI_INTERNAL explicit NavigationMesh(NavigationMeshImpl* impl);
index 6f29448..621fc82 100644 (file)
@@ -63,7 +63,7 @@ WayPointList PathFinder::FindPath(const Dali::Vector3& positionFrom, const Dali:
   return mImpl->FindPath(positionFrom, positionTo);
 }
 
-WayPointList PathFinder::FindPath(uint32_t polyIndexFrom, uint32_t polyIndexTo)
+WayPointList PathFinder::FindPath(FaceIndex polyIndexFrom, FaceIndex polyIndexTo)
 {
   return mImpl->FindPath(polyIndexFrom, polyIndexTo);
 }
index ee37d5e..5d2a6f1 100644 (file)
@@ -68,7 +68,7 @@ public:
    * @param[in] polyIndexTo Index of end polygon
    * @return List of waypoints for path or empty vector if no success
    */
-  virtual WayPointList FindPath(uint32_t polyIndexFrom, uint32_t polyIndexTo) = 0;
+  virtual WayPointList FindPath(FaceIndex polyIndexFrom, FaceIndex polyIndexTo) = 0;
 };
 
 /**
@@ -119,7 +119,7 @@ public:
    * @param[in] faceIndexTo Target face index
    * @return List of waypoints for path or empty list on failure
    */
-  WayPointList FindPath(uint32_t faceIndexFrom, uint32_t faceIndexTo);
+  WayPointList FindPath(FaceIndex faceIndexFrom, FaceIndex faceIndexTo);
 
 private:
   PathFinder() = delete;
index a398bad..6cc3d6a 100644 (file)
@@ -554,7 +554,7 @@ void CalculateGltf2BlendShapes(uint8_t* geometryBuffer, const std::vector<MeshDe
       std::vector<uint8_t>  buffer(bufferSize);
       std::vector<uint32_t> sparseIndices;
 
-      if(ReadAccessor(blendShape.tangents, buffers[blendShape.tangents.mBufferIdx].GetBufferStream(), buffer.data()), &sparseIndices)
+      if(ReadAccessor(blendShape.tangents, buffers[blendShape.tangents.mBufferIdx].GetBufferStream(), buffer.data(), &sparseIndices))
       {
         blendShape.tangents.mBlob.ApplyMinMax(static_cast<uint32_t>(bufferSize / sizeof(Vector3)), reinterpret_cast<float*>(buffer.data()), &sparseIndices);
 
index a8632e9..c00a87b 100644 (file)
@@ -283,10 +283,10 @@ void ModelRenderable::OnCreate(const NodeDefinition& nodeDefinition, NodeDefinit
 
   {
     mesh.first.mModelPrimitive = ModelPrimitive::New();
-    auto primitive            = mesh.first.mModelPrimitive;
+    auto primitive             = mesh.first.mModelPrimitive;
     GetImplementation(primitive).SetRenderer(renderer);
 
-    Index    envIndex         = resources.mMaterials[mMaterialIdx].first.mEnvironmentIdx;
+    Index    envIndex       = resources.mMaterials[mMaterialIdx].first.mEnvironmentIdx;
     uint32_t specularMipmap = resources.mEnvironmentMaps[envIndex].second.mSpecularMipmapLevels;
     GetImplementation(primitive).SetImageBasedLightTexture(resources.mEnvironmentMaps[envIndex].second.mDiffuse,
                                                            resources.mEnvironmentMaps[envIndex].second.mSpecular,
@@ -335,7 +335,7 @@ void ModelRenderable::OnCreate(const NodeDefinition& nodeDefinition, NodeDefinit
   renderer.RegisterProperty("uMask", mask);
   renderer.RegisterProperty("uAlphaThreshold", alphaCutoff);
 
-  Index    envIndex         = matDef.mEnvironmentIdx;
+  Index    envIndex       = matDef.mEnvironmentIdx;
   uint32_t specularMipmap = resources.mEnvironmentMaps[envIndex].second.mSpecularMipmapLevels;
   renderer.RegisterProperty(IBL_MAXLOD.data(), static_cast<float>(specularMipmap));
   renderer.RegisterProperty(IBL_INTENSITY_STRING.data(), resources.mEnvironmentMaps[envIndex].first.mIblIntensity);
@@ -344,8 +344,8 @@ void ModelRenderable::OnCreate(const NodeDefinition& nodeDefinition, NodeDefinit
   node.SetProperty(Actor::Property::COLOR, mColor);
 
   {
-    matDef.mMaterial = Material::New();
-    auto material    = matDef.mMaterial;
+    matDef.mMaterial            = Material::New();
+    auto     material           = matDef.mMaterial;
     uint32_t textureIndexOffset = (mesh.second.blendShapeGeometry) ? 1 : 0;
     uint32_t textureIndex       = 0;
     for(uint32_t i = 0; i < MAX_NUMBER_OF_MATERIAL_TEXTURE; ++i)
index 9b73271..02d056f 100644 (file)
@@ -108,7 +108,7 @@ struct DALI_SCENE3D_API SkinningShaderConfigurationRequest
 
   bool operator<(const SkinningShaderConfigurationRequest& other) const
   {
-    return mShader < other.mShader;
+    return mShader < other.mShader || (mShader == other.mShader && mPrimitive < other.mPrimitive);
   }
 };
 
@@ -124,7 +124,7 @@ struct DALI_SCENE3D_API BlendshapeShaderConfigurationRequest
 
   bool operator<(const BlendshapeShaderConfigurationRequest& other) const
   {
-    return mShader < other.mShader;
+    return mShader < other.mShader || (mShader == other.mShader && mPrimitive < other.mPrimitive);
   }
 };
 
index b1617de..10f6866 100644 (file)
@@ -44,21 +44,16 @@ namespace Dali::Scene3D::Loader
 {
 namespace
 {
-
 const std::map<Property::Type, Constraint (*)(Actor&, Property::Index)>& GetConstraintFactory()
 {
   static const std::map<Property::Type, Constraint (*)(Actor&, Property::Index)> sConstraintFactory = {
     {Property::Type::BOOLEAN,
      [](Actor& a, Property::Index i) {
-       return Constraint::New<bool>(a, i, [](bool& current, const PropertyInputContainer& inputs) {
-         current = inputs[0]->GetBoolean();
-       });
+       return Constraint::New<bool>(a, i, [](bool& current, const PropertyInputContainer& inputs) { current = inputs[0]->GetBoolean(); });
      }},
     {Property::Type::INTEGER,
      [](Actor& a, Property::Index i) {
-       return Constraint::New<int>(a, i, [](int& current, const PropertyInputContainer& inputs) {
-         current = inputs[0]->GetInteger();
-       });
+       return Constraint::New<int>(a, i, [](int& current, const PropertyInputContainer& inputs) { current = inputs[0]->GetInteger(); });
      }},
     {Property::Type::FLOAT,
      [](Actor& a, Property::Index i) {
@@ -203,47 +198,43 @@ public:
 
 private:
   NodeDefinition::CreateParams& mCreationContext;
-  std::vector<ModelNode>            mActorStack;
-  ModelNode                         mRoot;
+  std::vector<ModelNode>        mActorStack;
+  ModelNode                     mRoot;
 };
 
-void SortAndDeduplicateSkinningRequests(std::vector<SkinningShaderConfigurationRequest>& requests)
+template<typename RequestType>
+void SortAndDeduplicateRequests(std::vector<RequestType>& requests)
 {
-  // Sort requests by shaders.
+  // Sort requests by shaders and primitives.
   std::sort(requests.begin(), requests.end());
 
   // Remove duplicates.
-  auto   i           = requests.begin();
-  auto   iEnd        = requests.end();
-  Shader s           = i->mShader;
-  Index  skeletonIdx = i->mSkeletonIdx;
-  ++i;
+  auto iter    = requests.begin();
+  auto iterEnd = requests.end();
+
+  Shader         shader         = iter->mShader;
+  ModelPrimitive modelPrimitive = iter->mPrimitive;
+  ++iter;
   do
   {
-    // Multiple identical shader instances are removed.
-    while(i != iEnd && i->mShader == s)
+    // Multiple identical shader and primitive instances are removed.
+    while(iter != iterEnd && iter->mShader == shader && iter->mPrimitive == modelPrimitive)
     {
-      // Cannot have multiple skeletons input to the same shader.
-      // NOTE: DliModel now makes sure this doesn't happen.
-      DALI_ASSERT_ALWAYS(i->mSkeletonIdx == skeletonIdx &&
-                         "Skinning shader must not be shared between different skeletons.");
-
-      i->mShader = Shader();
-      ++i;
+      // Mark as removed
+      iter->mShader = Shader();
+      ++iter;
     }
 
-    if(i == iEnd)
+    if(iter == iterEnd)
     {
       break;
     }
-    s           = i->mShader;
-    skeletonIdx = i->mSkeletonIdx;
-    ++i;
+    shader         = iter->mShader;
+    modelPrimitive = iter->mPrimitive;
+    ++iter;
   } while(true);
 
-  requests.erase(std::remove_if(requests.begin(), requests.end(), [](const SkinningShaderConfigurationRequest& sscr) {
-                   return !sscr.mShader;
-                 }),
+  requests.erase(std::remove_if(requests.begin(), requests.end(), [](const RequestType& sscr) { return !sscr.mShader; }),
                  requests.end());
 }
 
@@ -628,9 +619,7 @@ NodeDefinition* SceneDefinition::FindNode(const std::string& name, Index* outInd
 {
   auto iBegin = mNodes.begin();
   auto iEnd   = mNodes.end();
-  auto iFind  = std::find_if(iBegin, iEnd, [&name](const std::unique_ptr<NodeDefinition>& nd) {
-    return nd->mName == name;
-  });
+  auto iFind  = std::find_if(iBegin, iEnd, [&name](const std::unique_ptr<NodeDefinition>& nd) { return nd->mName == name; });
 
   auto result = iFind != iEnd ? iFind->get() : nullptr;
   if(result && outIndex)
@@ -644,9 +633,7 @@ const NodeDefinition* SceneDefinition::FindNode(const std::string& name, Index*
 {
   auto iBegin = mNodes.begin();
   auto iEnd   = mNodes.end();
-  auto iFind  = std::find_if(iBegin, iEnd, [&name](const std::unique_ptr<NodeDefinition>& nd) {
-    return nd->mName == name;
-  });
+  auto iFind  = std::find_if(iBegin, iEnd, [&name](const std::unique_ptr<NodeDefinition>& nd) { return nd->mName == name; });
 
   auto result = iFind != iEnd ? iFind->get() : nullptr;
   if(result && outIndex)
@@ -660,9 +647,7 @@ Index SceneDefinition::FindNodeIndex(const NodeDefinition& node) const
 {
   auto iBegin = mNodes.begin();
   auto iEnd   = mNodes.end();
-  auto iFind  = std::find_if(iBegin, iEnd, [&node](const std::unique_ptr<NodeDefinition>& n) {
-    return n.get() == &node;
-  });
+  auto iFind  = std::find_if(iBegin, iEnd, [&node](const std::unique_ptr<NodeDefinition>& n) { return n.get() == &node; });
   return iFind != iEnd ? std::distance(iBegin, iFind) : INVALID_INDEX;
 }
 
@@ -814,7 +799,7 @@ void SceneDefinition::ConfigureSkinningShaders(const ResourceBundle&
     return;
   }
 
-  SortAndDeduplicateSkinningRequests(requests);
+  SortAndDeduplicateRequests(requests);
 
   for(auto& request : requests)
   {
@@ -828,7 +813,7 @@ void SceneDefinition::ConfigureSkinningShaders(const ResourceBundle&
     Index boneIdx = 0;
     for(auto& joint : skeleton.mJoints)
     {
-      auto  node  = GetNode(joint.mNodeIdx);
+      auto      node      = GetNode(joint.mNodeIdx);
       ModelNode modelNode = ModelNode::DownCast(rootActor.FindChildByName(node->mName));
       if(!modelNode)
       {
@@ -849,47 +834,24 @@ bool SceneDefinition::ConfigureBlendshapeShaders(const ResourceBundle&
     return true;
   }
 
-  // Sort requests by shaders.
-  std::sort(requests.begin(), requests.end());
-
-  // Remove duplicates.
-  auto   i    = requests.begin();
-  auto   iEnd = requests.end();
-  Shader s    = i->mShader;
-  ++i;
-  do
-  {
-    // Multiple identical shader instances are removed.
-    while(i != iEnd && i->mShader == s)
-    {
-      i->mShader = Shader();
-      ++i;
-    }
-
-    if(i == iEnd)
-    {
-      break;
-    }
-    s = i->mShader;
-    ++i;
-  } while(true);
+  SortAndDeduplicateRequests(requests);
 
   // Configure the rest.
   bool ok = true;
 
-  for(auto& i : requests)
+  for(auto& request : requests)
   {
     Index iNode;
-    if(FindNode(i.mNodeName, &iNode))
+    if(FindNode(request.mNodeName, &iNode))
     {
       const auto& node = GetNode(iNode);
 
-      const auto& mesh = resources.mMeshes[i.mMeshIdx];
+      const auto& mesh = resources.mMeshes[request.mMeshIdx];
 
       if(mesh.first.HasBlendShapes())
       {
-        Actor actor = rootActor.FindChildByName(node->mName);
-        Scene3D::ModelNode node = Scene3D::ModelNode::DownCast(actor);
+        Actor              actor = rootActor.FindChildByName(node->mName);
+        Scene3D::ModelNode node  = Scene3D::ModelNode::DownCast(actor);
         if(!node)
         {
           continue;
@@ -906,10 +868,10 @@ bool SceneDefinition::ConfigureBlendshapeShaders(const ResourceBundle&
         {
           data.unnormalizeFactors.push_back(factor);
         }
-        data.version = mesh.first.mBlendShapeVersion;
+        data.version      = mesh.first.mBlendShapeVersion;
         data.bufferOffset = mesh.second.blendShapeBufferOffset;
-        data.mActor = actor;
-        Internal::GetImplementation(node).SetBlendShapeData(data, i.mPrimitive);
+        data.mActor       = actor;
+        Internal::GetImplementation(node).SetBlendShapeData(data, request.mPrimitive);
       }
     }
   }
@@ -975,9 +937,8 @@ bool SceneDefinition::FindNode(const std::string& name, std::unique_ptr<NodeDefi
   // We're searching from the end assuming a higher probability of operations targeting
   // recently added nodes. (conf.: root, which is immovable, cannot be removed, and was
   // the first to be added, is index 0.)
-  auto iFind = std::find_if(mNodes.rbegin(), mNodes.rend(), [&name](const std::unique_ptr<NodeDefinition>& nd) {
-                 return nd->mName == name;
-               }).base();
+  auto iFind = std::find_if(mNodes.rbegin(), mNodes.rend(), [&name](const std::unique_ptr<NodeDefinition>& nd) { return nd->mName == name; })
+                 .base();
 
   const bool success = iFind != mNodes.begin();
   if(success && result)
index 5ac6317..5b9725a 100644 (file)
@@ -220,7 +220,7 @@ Dali::Accessibility::States ControlAccessible::CalculateStates()
   states[State::HIGHLIGHTABLE] = self.GetProperty<bool>(Toolkit::DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE);
   states[State::HIGHLIGHTED]   = GetCurrentlyHighlightedActor() == self;
   states[State::ENABLED]       = true;
-  states[State::SENSITIVE]     = true;
+  states[State::SENSITIVE]     = self.GetProperty<bool>(Actor::Property::SENSITIVE);
   states[State::VISIBLE]       = self.GetProperty<bool>(Actor::Property::VISIBLE);
   states[State::SHOWING]       = IsShowing();
   states[State::DEFUNCT]       = !self.GetProperty(Dali::DevelActor::Property::CONNECTED_TO_SCENE).Get<bool>();
index 7deaf7a..9e6d199 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_DEVEL_API_VISUALS_IMAGE_VISUAL_PROPERTIES_DEVEL_H
 
 /*
- * Copyright (c) 2020 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.
@@ -91,7 +91,7 @@ enum Type
    * Animation will play between the start frame and the end frame of the marker if one marker is specified.
    * Or animation will play between the start frame of the first marker and the end frame of the second marker if two markers are specified.
    *
-   * @details Name "playRange", Type Property::ARRAY of Property::INTEGER or Property::ARRAY of Property::STRING.
+   * @details Name "playRange", Type Property::ARRAY of Property::INTEGER or Property::ARRAY of Property::STRING or Property::STRING (one marker).
    * @note Default 0 and the total frame number.
    */
   PLAY_RANGE = ORIENTATION_CORRECTION + 4,
@@ -150,6 +150,9 @@ enum Type
   /**
    * @brief Whether to apply mask in loading time or rendering time.
    * @details Name "maskingType", type PlayState::Type (Property::INTEGER).
+   * In general, MASKING_ON_LOADING is the default behavior.
+   * However, if the visual uses an external texture, only MASKING_ON_RENDERING is possible.
+   * So we change its value to MASKING_ON_RENDERING even if the visual sets the MASKING_TYPE as MASKING_ON_LOADING when it uses external texture.
    * @note It is used in the ImageVisual and AnimatedImageVisual. The default is MASKING_ON_LOADING.
    */
   MASKING_TYPE = ORIENTATION_CORRECTION + 12
index b859b39..3ac6c81 100644 (file)
@@ -13,6 +13,13 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/builder/style.cpp
    ${toolkit_src_dir}/builder/tree-node-manipulator.cpp
    ${toolkit_src_dir}/builder/replacement.cpp
+   ${toolkit_src_dir}/particle-system/particle-impl.cpp
+   ${toolkit_src_dir}/particle-system/particle-domain-impl.cpp
+   ${toolkit_src_dir}/particle-system/particle-emitter-impl.cpp
+   ${toolkit_src_dir}/particle-system/particle-list-impl.cpp
+   ${toolkit_src_dir}/particle-system/particle-modifier-impl.cpp
+   ${toolkit_src_dir}/particle-system/particle-renderer-impl.cpp
+   ${toolkit_src_dir}/particle-system/particle-source-impl.cpp
    ${toolkit_src_dir}/texture-manager/texture-async-loading-helper.cpp
    ${toolkit_src_dir}/texture-manager/texture-cache-manager.cpp
    ${toolkit_src_dir}/texture-manager/texture-manager-impl.cpp
index 380da05..f5d8d03 100644 (file)
@@ -1014,24 +1014,17 @@ void KeyboardFocusManager::OnTouch(const TouchEvent& touch)
     {
       return;
     }
+    // If mClearFocusOnTouch is false, do not clear the focus indicator even if user touch the screen.
+    if(mClearFocusOnTouch)
+    {
+      ClearFocusIndicator();
+    }
+
     // If KEYBOARD_FOCUSABLE and TOUCH_FOCUSABLE is true, set focus actor
     if(hitActor && hitActor.GetProperty<bool>(Actor::Property::KEYBOARD_FOCUSABLE) && hitActor.GetProperty<bool>(DevelActor::Property::TOUCH_FOCUSABLE))
     {
-      // If mClearFocusOnTouch is false, do not clear the focus
-      if(mClearFocusOnTouch)
-      {
-        ClearFocus();
-      }
       SetCurrentFocusActor(hitActor);
     }
-    else
-    {
-      // If mClearFocusOnTouch is false, do not clear the focus indicator even if user touch the screen.
-      if(mClearFocusOnTouch)
-      {
-        ClearFocusIndicator();
-      }
-    }
   }
 }
 
index 63ef338..29ad984 100644 (file)
@@ -24,7 +24,7 @@ uniform mediump float borderlineWidth;
 uniform mediump float borderlineOffset;
 #endif
 #ifdef IS_REQUIRED_ROUNDED_CORNER
-uniform mediump vec4 cornerRadius;
+uniform highp vec4 cornerRadius;
 uniform mediump float cornerRadiusPolicy;
 #endif
 uniform mediump vec2 extraSize;
index 68179ab..b2b0864 100644 (file)
@@ -24,7 +24,7 @@ uniform mediump float borderlineWidth;
 uniform mediump float borderlineOffset;
 #endif
 #ifdef IS_REQUIRED_ROUNDED_CORNER
-uniform mediump vec4 cornerRadius;
+uniform highp vec4 cornerRadius;
 uniform mediump float cornerRadiusPolicy;
 #endif
 
index ceedb74..a1243ec 100644 (file)
@@ -24,7 +24,7 @@ uniform mediump float borderlineWidth;
 uniform mediump float borderlineOffset;
 #endif
 #ifdef IS_REQUIRED_ROUNDED_CORNER
-uniform mediump vec4 cornerRadius;
+uniform highp vec4 cornerRadius;
 uniform mediump float cornerRadiusPolicy;
 #endif
 #ifdef IS_REQUIRED_ALPHA_MASKING
diff --git a/dali-toolkit/internal/particle-system/particle-domain-impl.cpp b/dali-toolkit/internal/particle-system/particle-domain-impl.cpp
new file mode 100644 (file)
index 0000000..cbdee9b
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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-toolkit/internal/particle-system/particle-domain-impl.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-domain.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+// TODO: This needs physics modifier to be implemented (no use-case yet)
+}
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-domain-impl.h b/dali-toolkit/internal/particle-system/particle-domain-impl.h
new file mode 100644 (file)
index 0000000..5e3e1ea
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_DOMAIN_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_DOMAIN_H
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-domain.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-object.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleDomain : public Dali::BaseObject
+{
+};
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+
+namespace Dali::Toolkit::ParticleSystem
+{
+inline Internal::ParticleDomain& GetImplementation(ParticleSystem::ParticleDomain& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleDomain handle is empty");
+
+  BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<Internal::ParticleDomain&>(handle);
+}
+
+inline const Internal::ParticleDomain& GetImplementation(const ParticleSystem::ParticleDomain& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleDomain handle is empty");
+
+  const BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<const Internal::ParticleDomain&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_DOMAIN_H
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-emitter-impl.cpp b/dali-toolkit/internal/particle-system/particle-emitter-impl.cpp
new file mode 100644 (file)
index 0000000..e55a0e2
--- /dev/null
@@ -0,0 +1,446 @@
+/*
+ * 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-toolkit/internal/particle-system/particle-emitter-impl.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-modifier-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-renderer-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-source-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/common/stage-devel.h>
+#include <dali/devel-api/update/frame-callback-interface.h>
+#include <memory>
+#include <utility>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+
+constexpr uint32_t DEFAULT_PARTICLE_COUNT = 100u; ///< Default number of particles in system if not set by user
+
+/**
+ * Particle system frame callback to run modifiers and sources
+ */
+class FrameCallback : public Dali::FrameCallbackInterface
+{
+public:
+  /**
+   * @brief Constructor.
+   */
+  FrameCallback(Internal::ParticleEmitter* emitter)
+  : mEmitter(emitter)
+  {
+  }
+
+  ~FrameCallback() = default;
+
+private:
+  void Update(Dali::UpdateProxy& updateProxy, float elapsedSeconds) override
+  {
+    mEmitter->Update();
+  }
+
+  Internal::ParticleEmitter* mEmitter;
+};
+
+ParticleSystem::ParticleSource ParticleEmitter::GetSource() const
+{
+  return mParticleSource;
+}
+
+void ParticleEmitter::SetSource(const ParticleSystem::ParticleSource& source)
+{
+  mParticleStatusBits |= SOURCE_SET_STATUS_BIT;
+  mParticleSource = source;
+
+  // call the init function of source
+  GetImplementation(mParticleSource).GetUpdater().Init();
+}
+
+void ParticleEmitter::SetDomain(const ParticleSystem::ParticleDomain& domain)
+{
+  mParticleStatusBits |= DOMAIN_SET_STATUS_BIT;
+  mParticleDomain = domain;
+}
+
+void ParticleEmitter::SetRenderer(const ParticleSystem::ParticleRenderer& renderer)
+{
+  mParticleStatusBits |= RENDERER_SET_STATUS_BIT;
+  mParticleRenderer = renderer;
+  GetImplementation(mParticleRenderer).SetEmitter(this);
+}
+
+void ParticleEmitter::SetParticleCount(uint32_t maxParticleCount)
+{
+  // Default particle list has no data streams, it will replace old list
+  mParticleList = ParticleSystem::ParticleList::New(maxParticleCount,
+                                                    ParticleStream::POSITION_STREAM_BIT |
+                                                      ParticleStream::COLOR_STREAM_BIT |
+                                                      ParticleStream::VELOCITY_STREAM_BIT |
+                                                      ParticleStream::SCALE_STREAM_BIT |
+                                                      ParticleStream::LIFETIME_STREAM_BIT);
+}
+
+ParticleSystem::ParticleList& ParticleEmitter::GetParticleList()
+{
+  return mParticleList;
+}
+
+uint32_t ParticleEmitter::AddModifier(const ParticleSystem::ParticleModifier& modifier)
+{
+  mModifiers.emplace_back(modifier);
+  return mModifiers.size() - 1;
+}
+
+ParticleSystem::ParticleDomain ParticleEmitter::GetDomain() const
+{
+  return mParticleDomain;
+}
+
+ParticleSystem::ParticleRenderer ParticleEmitter::GetRenderer() const
+{
+  return mParticleRenderer;
+}
+
+ParticleSystem::ParticleModifier ParticleEmitter::GetModifierAt(uint32_t index)
+{
+  return index < mModifiers.size() ? mModifiers[index] : ParticleSystem::ParticleModifier();
+}
+
+void ParticleEmitter::RemoveModifierAt(uint32_t index)
+{
+  mModifiers.erase(mModifiers.begin()+index);
+}
+
+void ParticleEmitter::Start()
+{
+  if(mActor && IsComplete() && !(mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
+  {
+    if(mFrameCallback)
+    {
+      Stop();
+    }
+
+    GetImplementation(mParticleRenderer).Initialize();
+
+    mSystemStarted = true;
+    mParticleStatusBits &= ~(SIMULATION_STOPPED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
+    mParticleStatusBits |= SIMULATION_STARTED_STATUS_BIT;
+    mFrameCallback = std::make_unique<FrameCallback>(this);
+
+    // Attach renderer to an actor
+    auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
+    mActor.AddRenderer(renderer);
+    DevelStage::AddFrameCallback(Stage::GetCurrent(), *mFrameCallback, mActor);
+  }
+}
+
+void ParticleEmitter::Stop()
+{
+  if(mActor && IsComplete() && (mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
+  {
+    mSystemStarted = false;
+    mParticleStatusBits &= ~(SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
+    mParticleStatusBits |= SIMULATION_STOPPED_STATUS_BIT;
+    auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
+    mActor.RemoveRenderer(renderer);
+    DevelStage::RemoveFrameCallback(Stage::GetCurrent(), *mFrameCallback);
+  }
+}
+
+std::chrono::milliseconds ParticleEmitter::GetCurrentTimeMillis() const
+{
+  std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+    std::chrono::system_clock::now().time_since_epoch());
+  return ms;
+}
+
+void ParticleEmitter::Update()
+{
+  // Do not update if emitter setup isn't complete
+  if(!IsComplete())
+  {
+    return;
+  }
+
+  auto ms = GetCurrentTimeMillis();
+
+  if(mCurrentMilliseconds.count() == 0)
+  {
+    mCurrentMilliseconds = ms;
+  }
+
+  if(mLastUpdateMs.count() == 0)
+  {
+    mLastUpdateMs = ms;
+  }
+
+  float emissionDelta = 1.0f / float(mEmissionRatePerSecond); // time per one particle emission (TODO: add some randomness to it)
+
+  auto diffTime = double((ms - mCurrentMilliseconds).count()) / 1000.0;
+
+  uint32_t emissionCount = 0u;
+  if(diffTime >= emissionDelta)
+  {
+    emissionCount        = round(diffTime / emissionDelta);
+    mCurrentMilliseconds = ms;
+  }
+
+  // Update lifetimes and discard dead particles
+  auto& particles = mParticleList.GetActiveParticles();
+  auto  dt        = ms - mLastUpdateMs;
+  if(dt.count())
+  {
+    std::vector<int> toErase;
+    int              n = 0;
+    for(auto& p : particles)
+    {
+      auto& lifetime = p.Get<float>(ParticleStream::LIFETIME_STREAM_BIT);
+      lifetime -= (float(dt.count()) / 1000.0f);
+      if(lifetime <= 0.0f)
+      {
+        toErase.emplace_back(n);
+      }
+      ++n;
+    }
+
+    if(!toErase.empty())
+    {
+      int indexShift = 0;
+      for(auto& v : toErase)
+      {
+        GetImplementation(mParticleList).ReleaseParticle(v - indexShift);
+        ++indexShift;
+      }
+    }
+  }
+  mLastUpdateMs = ms;
+
+  // apply initial emission count
+  if(mSystemStarted)
+  {
+    emissionCount  = mEmissionCountOnStart;
+    mSystemStarted = false;
+  }
+
+  // Update source if there are any particles to be emitted
+  if(emissionCount)
+  {
+    // Apply active particles limiter
+    if(mActiveParticlesLimit && mParticleList.GetActiveParticleCount() + emissionCount > mActiveParticlesLimit)
+    {
+      emissionCount = mActiveParticlesLimit - mParticleList.GetActiveParticleCount();
+    }
+    UpdateSource(emissionCount);
+  }
+
+  // Update modifier stack
+  for(auto& modifier : mModifiers)
+  {
+    if(modifier)
+    {
+      // Parallel processing must be enabled in order to use MT mode
+      bool mt = GetImplementation(modifier).GetUpdater().IsMultiThreaded() && mParallelProcessing;
+
+      if(!mt) // single-threaded, update all particles in one go
+      {
+        GetImplementation(modifier).Update(mParticleList, 0, mParticleList.GetActiveParticleCount());
+      }
+      else
+      {
+        UpdateModifierMT(modifier);
+      }
+    }
+  }
+
+  UpdateDomain();
+}
+
+void ParticleEmitter::AttachTo(Actor actor)
+{
+  mActor = std::move(actor);
+}
+
+Actor ParticleEmitter::GetActor() const
+{
+  return mActor;
+}
+
+void ParticleEmitter::UpdateSource(uint32_t count)
+{
+  GetImplementation(mParticleSource).Update(mParticleList, count);
+}
+
+void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleModifier& modifier)
+{
+  auto& threadPool    = GetThreadPool();
+  auto  workerThreads = threadPool.GetWorkerCount();
+  auto  activeCount   = mParticleList.GetActiveParticleCount();
+
+  // at least 10 particles per worker thread (should be parametrized)
+  // If less, continue ST
+  if(activeCount < workerThreads * 10)
+  {
+    GetImplementation(modifier).Update(mParticleList, 0, activeCount);
+    return;
+  }
+
+  auto partial = mParticleList.GetActiveParticleCount() / workerThreads;
+
+  // make tasks
+  struct UpdateTask
+  {
+    UpdateTask(Internal::ParticleModifier& modifier, ParticleSystem::ParticleList& list, uint32_t first, uint32_t count)
+    : mModifier(modifier),
+      mList(list),
+      mFirst(first),
+      mCount(count)
+    {
+    }
+
+    Internal::ParticleModifier&   mModifier;
+    ParticleSystem::ParticleList& mList;
+    uint32_t                      mFirst;
+    uint32_t                      mCount;
+
+    void Update()
+    {
+      mModifier.Update(mList, mFirst, mCount);
+    }
+  };
+
+  std::vector<UpdateTask> updateTasks;
+  updateTasks.reserve(workerThreads);
+  std::vector<Task> tasks;
+
+  for(auto i = 0u; i < workerThreads; ++i)
+  {
+    auto index = i * partial;
+    auto count = partial;
+    if(i == workerThreads - 1 && index + count < activeCount)
+    {
+      count = activeCount - index;
+    }
+
+    updateTasks.emplace_back(GetImplementation(modifier), mParticleList, index, count);
+    tasks.emplace_back([&task = updateTasks.back()](uint32_t n)
+                       {
+      //printf("Updating modifier: %d\n", n);
+      task.Update(); });
+  }
+
+  auto future = threadPool.SubmitTasks(tasks, 0);
+  future->Wait();
+}
+
+void ParticleEmitter::UpdateDomain()
+{
+  // TODO
+}
+
+void ParticleEmitter::SetEmissionRate(uint32_t ratePerSecond)
+{
+  mEmissionRatePerSecond = ratePerSecond;
+}
+
+uint32_t ParticleEmitter::GetEmissionRate() const
+{
+  return mEmissionRatePerSecond;
+}
+
+void ParticleEmitter::EnableParallelProcessing(bool enabled)
+{
+  mParallelProcessing = enabled;
+}
+
+bool ParticleEmitter::IsParallelProcessingEnabled() const
+{
+  return mParallelProcessing;
+}
+
+void ParticleEmitter::SetInitialParticleCount(uint32_t count)
+{
+  mEmissionCountOnStart = count;
+}
+
+uint32_t ParticleEmitter::GetInitialParticleCount() const
+{
+  return mEmissionCountOnStart;
+}
+
+void ParticleEmitter::SetActiveParticlesLimit(uint32_t count)
+{
+  mActiveParticlesLimit = count;
+}
+
+uint32_t ParticleEmitter::GetActiveParticlesLimit() const
+{
+  return mActiveParticlesLimit;
+}
+
+ParticleSystem::ParticleEmitter::Status ParticleEmitter::GetStatus() const
+{
+  auto statusMask = SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT | SIMULATION_STOPPED_STATUS_BIT;
+  auto status     = (mParticleStatusBits & statusMask);
+
+  if(status & SIMULATION_PAUSED_STATUS_BIT)
+  {
+    return ParticleSystem::ParticleEmitter::Status::PAUSED;
+  }
+  else if(status & SIMULATION_STOPPED_STATUS_BIT)
+  {
+    return ParticleSystem::ParticleEmitter::Status::STOPPED;
+  }
+  else if(status & SIMULATION_STARTED_STATUS_BIT)
+  {
+    return ParticleSystem::ParticleEmitter::Status::STARTED;
+  }
+  else
+  {
+    return !IsComplete() ? ParticleSystem::ParticleEmitter::Status::INCOMPLETE : ParticleSystem::ParticleEmitter::Status::READY;
+  }
+}
+
+ParticleEmitter::ParticleEmitter()
+{
+  // Necessary to be called to initialize internal ParticleList
+  SetParticleCount(DEFAULT_PARTICLE_COUNT);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+namespace Dali::Toolkit::ParticleSystem
+{
+Dali::ThreadPool& GetThreadPool()
+{
+  static std::unique_ptr<Dali::ThreadPool> gThreadPool{nullptr};
+  static std::once_flag                    onceFlag;
+
+  // Intialize thread pool if not there yet, make sure it happens once and it's synchronized!,
+  // NOTE: this function shouldn't be called from multiple thread anyway
+  if(!gThreadPool)
+  {
+    std::call_once(onceFlag, [&threadPool = gThreadPool]
+                   { threadPool = std::make_unique<Dali::ThreadPool>();
+                     threadPool->Initialize(4u); });
+  }
+
+  return *gThreadPool;
+}
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-emitter-impl.h b/dali-toolkit/internal/particle-system/particle-emitter-impl.h
new file mode 100644 (file)
index 0000000..2f1dcc7
--- /dev/null
@@ -0,0 +1,186 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_EMITTER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_EMITTER_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/public-api/adaptor-framework/timer.h>
+#include <dali/public-api/object/base-object.h>
+#include <chrono>
+#include <ctime>
+#include <memory>
+
+// For multithreading update
+#include <dali/devel-api/threading/thread-pool.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-domain.h>
+#include <dali-toolkit/public-api/particle-system/particle-emitter.h>
+#include <dali-toolkit/public-api/particle-system/particle-list.h>
+#include <dali-toolkit/public-api/particle-system/particle-modifier.h>
+#include <dali-toolkit/public-api/particle-system/particle-renderer.h>
+#include <dali-toolkit/public-api/particle-system/particle-source.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class FrameCallback;
+class ParticleEmitter : public Dali::BaseObject, public Dali::ConnectionTracker
+{
+public:
+  /**
+   * @brief Constructor
+   */
+  ParticleEmitter();
+
+  /**
+   * @brief Destructor
+   */
+  ~ParticleEmitter() override = default;
+
+  /**
+   * @brief Tests whether emitter is complete (ready for simulation)
+   *
+   * @return True if emitter is complete, false otherwise
+   */
+  [[nodiscard]] bool IsComplete() const
+  {
+    return (mParticleStatusBits & STATUS_COMPLETE_BITS) == STATUS_COMPLETE_BITS;
+  }
+
+  [[nodiscard]] ParticleSystem::ParticleSource GetSource() const;
+
+  void SetSource(const ParticleSystem::ParticleSource& source);
+
+  [[nodiscard]] ParticleSystem::ParticleDomain GetDomain() const;
+
+  void SetDomain(const ParticleSystem::ParticleDomain& domain);
+
+  [[nodiscard]] ParticleSystem::ParticleRenderer GetRenderer() const;
+
+  [[nodiscard]] ParticleSystem::ParticleModifier GetModifierAt(uint32_t index);
+
+  void RemoveModifierAt(uint32_t index);
+
+  void SetRenderer(const ParticleSystem::ParticleRenderer& renderer);
+
+  void SetParticleCount(uint32_t maxParticleCount);
+
+  ParticleSystem::ParticleList& GetParticleList();
+
+  uint32_t AddModifier(const ParticleSystem::ParticleModifier& modifier);
+
+  void AttachTo(Actor actor);
+
+  [[nodiscard]] Actor GetActor() const;
+
+  void Update();
+
+  void UpdateSource(uint32_t count);
+
+  void UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleModifier& modifier);
+
+  void UpdateDomain();
+
+  void SetEmissionRate(uint32_t ratePerSecond);
+
+  [[nodiscard]] uint32_t GetEmissionRate() const;
+
+  void SetInitialParticleCount(uint32_t count);
+
+  [[nodiscard]] uint32_t GetInitialParticleCount() const;
+
+  void Start();
+
+  void Stop();
+
+  void EnableParallelProcessing(bool enabled);
+
+  [[nodiscard]] bool IsParallelProcessingEnabled() const;
+
+  void SetActiveParticlesLimit(uint32_t count);
+
+  [[nodiscard]] uint32_t GetActiveParticlesLimit() const;
+
+  [[nodiscard]] ParticleSystem::ParticleEmitter::Status GetStatus() const;
+
+  [[nodiscard]] std::chrono::milliseconds GetCurrentTimeMillis() const;
+
+  // All these bits must be set in order to consider emitter COMPLETE
+  const uint32_t SOURCE_SET_STATUS_BIT   = 1 << 0;
+  const uint32_t RENDERER_SET_STATUS_BIT = 1 << 1;
+  const uint32_t DOMAIN_SET_STATUS_BIT   = 1 << 2;
+
+  // 1. Only one of these flags can be set at a time
+  // 2. They are invalid as long as emitter is INCOMPLETE
+  const uint32_t SIMULATION_STARTED_STATUS_BIT = 1 << 3;
+  const uint32_t SIMULATION_PAUSED_STATUS_BIT  = 1 << 4;
+  const uint32_t SIMULATION_STOPPED_STATUS_BIT = 1 << 5;
+
+  const uint32_t STATUS_COMPLETE_BITS = SOURCE_SET_STATUS_BIT | RENDERER_SET_STATUS_BIT | DOMAIN_SET_STATUS_BIT;
+
+  ParticleSystem::ParticleSource mParticleSource; ///< Current particle source object
+  ParticleSystem::ParticleDomain mParticleDomain; ///< Current particle domain object
+
+  uint8_t mParticleStatusBits{0u}; ///< Current status of the emitter
+
+  // List of particles
+  ParticleSystem::ParticleList mParticleList;
+
+  std::vector<ParticleSystem::ParticleModifier> mModifiers;
+
+  ParticleSystem::ParticleRenderer mParticleRenderer;
+
+  Actor mActor;
+
+  uint32_t                  mEmissionRatePerSecond{1u};
+  std::atomic<uint32_t>     mEmissionCountOnStart{0u};
+  std::atomic<uint32_t>     mActiveParticlesLimit{0u}; ///< 0 - unlimited
+  std::atomic<bool>         mSystemStarted{false};
+  std::chrono::milliseconds mCurrentMilliseconds{0};
+  std::chrono::milliseconds mLastUpdateMs{0};
+
+  bool                           mParallelProcessing{false};
+  std::unique_ptr<FrameCallback> mFrameCallback;
+};
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+
+namespace Dali::Toolkit::ParticleSystem
+{
+// Returns thread pool shared by whole particle system
+Dali::ThreadPool& GetThreadPool();
+
+inline Internal::ParticleEmitter& GetImplementation(ParticleSystem::ParticleEmitter& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleEmitter handle is empty");
+
+  BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<Internal::ParticleEmitter&>(handle);
+}
+
+inline const Internal::ParticleEmitter& GetImplementation(const ParticleSystem::ParticleEmitter& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleEmitter handle is empty");
+
+  const BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<const Internal::ParticleEmitter&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_EMITTER_H
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-impl.cpp b/dali-toolkit/internal/particle-system/particle-impl.cpp
new file mode 100644 (file)
index 0000000..2a5ed3b
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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-toolkit/internal/particle-system/particle-impl.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+
+Particle::Particle(Internal::ParticleList& ownerList, uint32_t index)
+: mOwnerList(ownerList),
+  mIndex(index)
+{
+}
+
+void* Particle::Get(ParticleStreamTypeFlagBit streamBit)
+{
+  auto streamIndex = mOwnerList.GetDefaultStreamIndex(streamBit);
+  auto dataSize    = mOwnerList.GetStreamDataTypeSize(streamIndex);
+  return reinterpret_cast<uint8_t*>(mOwnerList.GetDefaultStream(streamBit)) + (mIndex * dataSize);
+}
+
+void* Particle::GetByIndex(uint32_t streamIndex)
+{
+  auto  dataSize = mOwnerList.GetStreamDataTypeSize(streamIndex);
+  auto* ptr      = reinterpret_cast<uint8_t*>(mOwnerList.GetRawStream(streamIndex));
+  return reinterpret_cast<uint8_t*>(ptr + (mIndex * dataSize));
+}
+
+uint32_t Particle::GetIndex() const
+{
+  return mIndex;
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-impl.h b/dali-toolkit/internal/particle-system/particle-impl.h
new file mode 100644 (file)
index 0000000..08aea33
--- /dev/null
@@ -0,0 +1,74 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_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.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/math/quaternion.h>
+#include <dali/public-api/math/vector3.h>
+#include <dali/public-api/object/base-object.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-list.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class Particle : public Dali::BaseObject
+{
+public:
+  ~Particle() = default;
+
+  Particle(Internal::ParticleList& ownerList, uint32_t index);
+
+  void* Get(ParticleStreamTypeFlagBit streamBit);
+
+  void* GetByIndex(uint32_t streamIndex);
+
+  uint32_t GetIndex() const;
+
+private:
+  Internal::ParticleList& mOwnerList;
+  uint32_t                mIndex;
+};
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+
+namespace Dali::Toolkit::ParticleSystem
+{
+inline Internal::Particle& GetImplementation(ParticleSystem::Particle& particle)
+{
+  DALI_ASSERT_ALWAYS(particle && "Particle handle is empty");
+
+  BaseObject& handle = particle.GetBaseObject();
+
+  return static_cast<Internal::Particle&>(handle);
+}
+
+inline const Internal::Particle& GetImplementation(const ParticleSystem::Particle& particle)
+{
+  DALI_ASSERT_ALWAYS(particle && "Particle handle is empty");
+
+  const BaseObject& handle = particle.GetBaseObject();
+
+  return static_cast<const Internal::Particle&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_H
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-list-impl.cpp b/dali-toolkit/internal/particle-system/particle-list-impl.cpp
new file mode 100644 (file)
index 0000000..550cbd8
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * 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-toolkit/internal/particle-system/particle-list-impl.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/particle-system/particle-impl.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector3>::GetType()
+{
+  return ParticleStream::StreamDataType::FLOAT3;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector4>::GetType()
+{
+  return ParticleStream::StreamDataType::FLOAT4;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector2>::GetType()
+{
+  return ParticleStream::StreamDataType::FLOAT2;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<float>::GetType()
+{
+  return ParticleStream::StreamDataType::FLOAT;
+}
+
+ParticleList::ParticleList(uint32_t capacity, ParticleSystem::ParticleList::ParticleStreamTypeFlags streamFlags)
+{
+  // capacity makes for max immutable particle count
+  mMaxParticleCount = capacity;
+
+  // initialize built-in streams and build map (to optimize later)
+  if(streamFlags & ParticleStream::POSITION_STREAM_BIT)
+  {
+    AddStream(Vector3::ZERO, "aStreamPosition", false);
+    mBuiltInStreamMap[uint32_t(ParticleStream::POSITION_STREAM_BIT)] = mDataStreams.size() - 1;
+  }
+  if(streamFlags & ParticleStream::ROTATION_STREAM_BIT)
+  {
+    AddStream(Vector4::ZERO, "aStreamRotation", false);
+    mBuiltInStreamMap[uint32_t(ParticleStream::ROTATION_STREAM_BIT)] = mDataStreams.size() - 1;
+  }
+  if(streamFlags & ParticleStream::SCALE_STREAM_BIT)
+  {
+    AddStream(Vector3::ONE, "aStreamScale", false);
+    mBuiltInStreamMap[uint32_t(ParticleStream::SCALE_STREAM_BIT)] = mDataStreams.size() - 1;
+  }
+  if(streamFlags & ParticleStream::VELOCITY_STREAM_BIT)
+  {
+    AddStream(Vector3::ZERO, "aStreamVelocity", false);
+    mBuiltInStreamMap[uint32_t(ParticleStream::VELOCITY_STREAM_BIT)] = mDataStreams.size() - 1;
+  }
+  if(streamFlags & ParticleStream::COLOR_STREAM_BIT)
+  {
+    AddStream(Color::YELLOW, "aStreamColor", false);
+    mBuiltInStreamMap[uint32_t(ParticleStream::COLOR_STREAM_BIT)] = mDataStreams.size() - 1;
+  }
+  if(streamFlags & ParticleStream::OPACITY_STREAM_BIT)
+  {
+    AddStream(0.0f, "aStreamOpacity", false);
+    mBuiltInStreamMap[uint32_t(ParticleStream::OPACITY_STREAM_BIT)] = mDataStreams.size() - 1;
+  }
+  if(streamFlags & ParticleStream::LIFETIME_STREAM_BIT)
+  {
+    AddStream(0.0f, "aStreamLifetime", false);
+    mBuiltInStreamMap[uint32_t(ParticleStream::LIFETIME_STREAM_BIT)] = mDataStreams.size() - 1;
+  }
+
+  // create free chain
+  mFreeChain.resize(capacity);
+  for(auto i = 0u; i < mFreeChain.size(); ++i)
+  {
+    mFreeChain[i] = i + 1;
+  }
+  mFreeChain[mFreeChain.size() - 1] = 0;
+  mFreeIndex                        = 0;
+}
+
+ParticleList::~ParticleList() = default;
+
+uint32_t ParticleList::AddStream(uint32_t sizeOfDataType, const void* defaultValue, ParticleStream::StreamDataType dataType, const char* streamName, bool localStream)
+{
+  mDataStreams.emplace_back(new ParticleDataStream(mMaxParticleCount, sizeOfDataType, defaultValue, dataType));
+  if(streamName)
+  {
+    mDataStreams.back()->SetStreamName(streamName);
+  }
+
+  mDataStreams.back()->SetStreamLocal(localStream);
+
+  // Update element size
+  mParticleStreamElementSize          = 0;
+  mParticleStreamElementSizeWithLocal = 0;
+  for(auto& ds : mDataStreams)
+  {
+    if(!ds->localStream)
+    {
+      mParticleStreamElementSize += ds->dataSize;
+    }
+    mParticleStreamElementSizeWithLocal += ds->dataSize;
+  }
+
+  return mDataStreams.size() - 1;
+}
+
+void* ParticleList::GetRawStream(uint32_t index)
+{
+  if(index < mDataStreams.size() && mDataStreams[index])
+  {
+    return mDataStreams[index]->data.data();
+  }
+  return nullptr;
+}
+
+uint32_t ParticleList::GetStreamCount() const
+{
+  return mDataStreams.size();
+}
+
+uint32_t ParticleList::GetParticleCount() const
+{
+  return mMaxParticleCount;
+}
+
+uint32_t ParticleList::GetActiveParticleCount() const
+{
+  return mParticles.size();
+}
+
+ParticleStream::StreamDataType ParticleList::GetStreamDataType(uint32_t streamIndex)
+{
+  return mDataStreams[streamIndex]->type;
+}
+
+const std::string& ParticleList::GetStreamName(uint32_t streamIndex) const
+{
+  return mDataStreams[streamIndex]->streamName;
+}
+
+bool ParticleList::IsStreamLocal(uint32_t streamIndex) const
+{
+  return mDataStreams[streamIndex]->localStream;
+}
+
+uint32_t ParticleList::GetStreamDataTypeSize(uint32_t streamIndex) const
+{
+  return mDataStreams[streamIndex]->dataSize;
+}
+
+ParticleSystem::Particle ParticleList::NewParticle(float lifetime)
+{
+  if(mParticles.size() < mMaxParticleCount)
+  {
+    auto newIndex = int32_t(mFreeIndex);
+    mFreeIndex    = int32_t(mFreeChain[mFreeIndex]);
+    mAliveParticleCount++;
+
+    // Add particle
+    mParticles.emplace_back(new Internal::Particle(*this, newIndex));
+
+    // Set particle lifetime
+    auto& particle = mParticles.back();
+
+    particle.Get<float>(ParticleStream::LIFETIME_STREAM_BIT) = lifetime;
+
+    return mParticles.back();
+  }
+  return {nullptr};
+}
+
+uint32_t ParticleList::GetStreamElementSize(bool includeLocalStream)
+{
+  if(includeLocalStream)
+  {
+    return mParticleStreamElementSizeWithLocal;
+  }
+  else
+  {
+    return mParticleStreamElementSize;
+  }
+}
+
+void ParticleList::ReleaseParticle(uint32_t particleIndex)
+{
+  auto it = mParticles.begin();
+  std::advance(it, particleIndex);
+
+  // Point at this slot of memory as next free slot
+  auto& p = *it;
+  if(mFreeIndex)
+  {
+    mFreeChain[p.GetIndex()] = mFreeIndex;
+    mFreeIndex               = p.GetIndex();
+  }
+
+  // Remove particle from the list
+  mParticles.erase(it);
+  mAliveParticleCount--;
+}
+
+void* ParticleList::GetDefaultStream(ParticleStreamTypeFlagBit streamBit)
+{
+  return GetRawStream(mBuiltInStreamMap[streamBit]);
+}
+
+uint32_t ParticleList::GetDefaultStreamIndex(ParticleStreamTypeFlagBit streamBit)
+{
+  return mBuiltInStreamMap[uint32_t(streamBit)];
+}
+
+std::list<ParticleSystem::Particle>& ParticleList::GetParticles()
+{
+  return mParticles;
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
diff --git a/dali-toolkit/internal/particle-system/particle-list-impl.h b/dali-toolkit/internal/particle-system/particle-list-impl.h
new file mode 100644 (file)
index 0000000..6f1ddea
--- /dev/null
@@ -0,0 +1,231 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_LIST_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_LIST_H
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-list.h>
+#include <dali-toolkit/public-api/particle-system/particle.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-object.h>
+#include <dali/public-api/common/vector-wrapper.h>
+#include <dali/public-api/common/list-wrapper.h>
+#include <dali/devel-api/common/map-wrapper.h>
+#include <algorithm>
+#include <memory>
+
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+template<class T>
+struct StreamDataTypeWrapper
+{
+  static ParticleStream::StreamDataType GetType()
+  {
+    return {};
+  }
+};
+
+struct ParticleDataStream
+{
+  ~ParticleDataStream() = default;
+  template<class T>
+  ParticleDataStream(uint32_t capacity, const T& defaultValue, ParticleStream::StreamDataType dataType)
+  : ParticleDataStream(capacity, sizeof(T), &defaultValue, dataType)
+  {
+  }
+
+  /**
+   * Creates new stream of requested capacity and (optionally) fills with default data
+   */
+  ParticleDataStream(uint32_t capacity, uint32_t dataSize, const void* defaultValue, ParticleStream::StreamDataType dataType)
+  {
+    this->capacity = capacity;
+    data.resize(capacity * dataSize);
+    if(defaultValue)
+    {
+      for(auto i = 0u; i < capacity; ++i)
+      {
+        auto dstPtr = data.data() + (i * dataSize);
+        std::copy(reinterpret_cast<const uint8_t*>(defaultValue), reinterpret_cast<const uint8_t*>(defaultValue) + dataSize, dstPtr);
+      }
+    }
+    type           = dataType;
+    alive          = 0u;
+    this->dataSize = dataSize;
+  }
+
+  void SetStreamName(const char* name)
+  {
+    streamName = name;
+  }
+
+  void SetStreamLocal(bool local)
+  {
+    localStream = local;
+  }
+
+  /**
+   * Converts raw data into requested type (does not guarantee compatibility)
+   */
+  template<class T>
+  T* GetAs()
+  {
+    return reinterpret_cast<T*>(data.data());
+  }
+
+  ParticleStream::StreamDataType type;
+  std::vector<uint8_t>           data;
+  std::string                    streamName;
+  uint32_t                       alive{0u};
+  uint32_t                       capacity;
+  uint32_t                       dataSize;
+  bool                           localStream{true};
+};
+
+/**
+ * Particle list stores particle-specific data and manages the particles memory
+ * It can return a sub-list.
+ *
+ * ParticleList manages the storage memory.
+ *
+ *
+ */
+class ParticleList : public Dali::BaseObject
+{
+public:
+
+  ParticleList(uint32_t capacity, ParticleSystem::ParticleList::ParticleStreamTypeFlags streamFlags);
+
+  ~ParticleList();
+
+  /**
+   * Returns raw pointer to the stream data
+   */
+  void* GetRawStream(uint32_t index);
+
+  /**
+   * Returns number of available data streams
+   * @return
+   */
+  uint32_t GetStreamCount() const;
+
+  /**
+   * Returns number of particles per list
+   * @return
+   */
+  uint32_t GetParticleCount() const;
+
+  /**
+   * Returns number of currently active particles
+   * @return
+   */
+  uint32_t GetActiveParticleCount() const;
+
+  /**
+   * Returns stream data-type
+   * @param streamIndex
+   * @return
+   */
+  ParticleStream::StreamDataType GetStreamDataType(uint32_t streamIndex);
+
+  /**
+   * Returns stream data type size
+   * @param streamIndex
+   * @return
+   */
+  [[nodiscard]] uint32_t GetStreamDataTypeSize(uint32_t streamIndex) const;
+
+  [[nodiscard]] const std::string& GetStreamName(uint32_t streamIndex) const;
+
+  [[nodiscard]] bool IsStreamLocal(uint32_t streamIndex) const;
+
+  /**
+   * Allocates new particle in the streams
+   * @param lifetime
+   * @return
+   */
+  ParticleSystem::Particle NewParticle(float lifetime);
+
+  void* GetDefaultStream(ParticleStreamTypeFlagBit streamBit);
+
+  uint32_t GetDefaultStreamIndex(ParticleStreamTypeFlagBit streamBit);
+
+  std::list<ParticleSystem::Particle>& GetParticles();
+
+  void ReleaseParticle(uint32_t particleIndex);
+
+  uint32_t GetStreamElementSize(bool includeLocalStream);
+
+private:
+  template<class T>
+  uint32_t AddStream(const T& defaultValue, const char* streamName, bool localStream)
+  {
+    return AddStream(sizeof(T), &defaultValue, StreamDataTypeWrapper<T>::GetType(), streamName, localStream);
+  }
+
+public:
+  /**
+   * Adds new stream and returns index
+   */
+  uint32_t AddStream(uint32_t sizeOfDataType, const void* defaultValue, ParticleStream::StreamDataType dataType, const char* streamName, bool localStream);
+
+private:
+  std::vector<char> mBuffer[2];
+
+  uint32_t mAliveParticleCount{0u};
+  uint32_t mMaxParticleCount;
+
+  // Data storage
+  std::vector<std::unique_ptr<ParticleDataStream>> mDataStreams;
+
+  std::vector<uint32_t> mFreeChain;
+  int32_t               mFreeIndex{0u};
+
+  std::map<uint32_t, uint32_t> mBuiltInStreamMap;
+
+  std::list<ParticleSystem::Particle> mParticles;
+
+  uint32_t mParticleStreamElementSizeWithLocal{0u};
+  uint32_t mParticleStreamElementSize{0u};
+};
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+namespace Dali::Toolkit::ParticleSystem
+{
+inline Internal::ParticleList& GetImplementation(ParticleSystem::ParticleList& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleList handle is empty");
+
+  BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<Internal::ParticleList&>(handle);
+}
+
+inline const Internal::ParticleList& GetImplementation(const ParticleSystem::ParticleList& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleList handle is empty");
+
+  const BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<const Internal::ParticleList&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_LIST_H
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-modifier-impl.cpp b/dali-toolkit/internal/particle-system/particle-modifier-impl.cpp
new file mode 100644 (file)
index 0000000..c00d71c
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-modifier-impl.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+ParticleModifier::ParticleModifier(std::unique_ptr<ParticleModifierInterface>&& updater)
+{
+  mUpdater = std::move(updater);
+}
+
+void ParticleModifier::Update(ParticleSystem::ParticleList& list, uint32_t first, uint32_t count)
+{
+  mUpdater->Update(list, first, count);
+}
+
+ParticleModifierInterface& ParticleModifier::GetUpdater()
+{
+  return *mUpdater;
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
diff --git a/dali-toolkit/internal/particle-system/particle-modifier-impl.h b/dali-toolkit/internal/particle-system/particle-modifier-impl.h
new file mode 100644 (file)
index 0000000..75cbf5f
--- /dev/null
@@ -0,0 +1,64 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_MODIFIER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_MODIFIER_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.
+ *
+ */
+
+#include <dali-toolkit/public-api/particle-system/particle-modifier.h>
+#include <dali/public-api/object/base-object.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleList;
+class ParticleEmitter;
+class ParticleModifier : public Dali::BaseObject
+{
+public:
+  ParticleModifier(std::unique_ptr<ParticleModifierInterface>&& updater);
+
+  void Update(ParticleSystem::ParticleList& list, uint32_t first, uint32_t count);
+
+  ParticleModifierInterface& GetUpdater();
+
+private:
+  std::unique_ptr<ParticleModifierInterface> mUpdater;
+};
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+
+namespace Dali::Toolkit::ParticleSystem
+{
+inline Internal::ParticleModifier& GetImplementation(ParticleSystem::ParticleModifier& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleModifier handle is empty");
+
+  BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<Internal::ParticleModifier&>(handle);
+}
+
+inline const Internal::ParticleModifier& GetImplementation(const ParticleSystem::ParticleModifier& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleModifier handle is empty");
+
+  const BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<const Internal::ParticleModifier&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_MODIFIER_H
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-renderer-impl.cpp b/dali-toolkit/internal/particle-system/particle-renderer-impl.cpp
new file mode 100644 (file)
index 0000000..b86c434
--- /dev/null
@@ -0,0 +1,463 @@
+/*
+ * 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.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-emitter-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-renderer-impl.h>
+#include <dali/devel-api/rendering/renderer-devel.h>
+
+#include <dali/devel-api/actors/actor-devel.h>
+#include <dali/devel-api/common/capabilities.h>
+#include <dali/graphics-api/graphics-buffer.h>
+#include <dali/graphics-api/graphics-controller.h>
+#include <dali/graphics-api/graphics-program.h>
+#include <dali/graphics-api/graphics-shader.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+ParticleRenderer::ParticleRenderer()
+{
+  mStreamBufferUpdateCallback = Dali::VertexBufferUpdateCallback::New(this, &ParticleRenderer::OnStreamBufferUpdate);
+}
+
+void ParticleRenderer::SetEmitter(ParticleSystem::Internal::ParticleEmitter* emitter)
+{
+  mEmitter = emitter;
+}
+
+void ParticleRenderer::SetTexture(const Dali::Texture& texture)
+{
+  mTexture = texture;
+}
+
+void ParticleRenderer::SetBlendingMode(BlendingMode blendingMode)
+{
+  mBlendingMode = blendingMode;
+}
+
+void ParticleRenderer::CreateShader()
+{
+  // Create shader dynamically
+  auto& list        = GetImplementation(mEmitter->GetParticleList());
+  auto  streamCount = list.GetStreamCount();
+
+  static const char* ATTR_GLSL_TYPES[] =
+    {
+      "float", "vec2", "vec3", "vec4", "int", "ivec2", "ivec3", "ivec4"};
+
+  static const Property::Type ATTR_TYPES[] =
+    {
+      Property::Type::FLOAT,
+      Property::Type::VECTOR2,
+      Property::Type::VECTOR3,
+      Property::Type::VECTOR4,
+      Property::Type::INTEGER,
+      Property::Type::VECTOR2, // This represents floats but by binary write it shouldn't matter (?)
+      Property::Type::VECTOR3,
+      Property::Type::VECTOR4,
+    };
+
+  struct Vertex2D
+  {
+    Vertex2D(const Vector2& _co, const Vector2& _uv)
+    : co(_co),
+      uv(_uv)
+    {
+    }
+    Dali::Vector2 co{};
+    Dali::Vector2 uv{};
+  };
+
+  uint32_t      streamElementSize = 0u;
+  Property::Map streamAtttributes;
+
+  std::stringstream ss;
+  for(auto i = 0u; i < streamCount; ++i)
+  {
+    // Don't add local streams to the shader
+    if(!list.IsStreamLocal(i))
+    {
+      uint32_t    dataTypeSize  = list.GetStreamDataTypeSize(i);
+      auto        dataTypeIndex = uint32_t(list.GetStreamDataType(i));
+      const auto& streamName    = list.GetStreamName(i);
+      streamElementSize += dataTypeSize;
+      char key[256];
+      if(streamName.empty())
+      {
+        sprintf(key, "aStreamAttr_%d", i);
+      }
+      else
+      {
+        sprintf(key, "%s", streamName.c_str());
+      }
+      streamAtttributes.Add(key, ATTR_TYPES[dataTypeIndex]);
+
+      // Add shader attribute line
+      ss << "INPUT mediump " << ATTR_GLSL_TYPES[dataTypeIndex] << " " << key << ";\n";
+    }
+  }
+
+  auto streamAttributesStr = ss.str();
+
+  /**
+   * - The MVP comes from the Actor that the particle renderer is attached to
+   * - Attributes are added dynamically based on the particle system properties
+   * - There are two buffers bound
+   *   * Geometry buffer (in this instance, a quad)
+   *   * ParticleSystem stream buffer with interleaved data
+   * - ParticleSystem buffer is being updated every frame
+   */
+  std::string vertexShaderCode = streamAttributesStr + std::string(
+
+                                                         "INPUT mediump vec2 aPosition;\n\
+      INPUT mediump vec2 aTexCoords;\n\
+      \n\
+      uniform mediump mat4   uMvpMatrix;\n\
+      uniform mediump vec3   uSize;\n\
+      uniform lowp vec4      uColor;\n\
+      \
+      OUTPUT mediump vec2   vTexCoord;\n\
+      OUTPUT mediump vec4 vColor;\n\
+      \n\
+      void main()\n\
+      {\n\
+        vec4 pos = vec4(aPosition, 0.0, 1.0) * vec4(aStreamScale, 1.0);\n\
+        vec4 position =  pos + vec4(aStreamPosition, 0.0);\n\
+        vTexCoord     = aTexCoords;\n\
+        vColor = uColor * aStreamColor;\n\
+        gl_Position   = uMvpMatrix * position ;\n\
+      }\n");
+
+  std::string fragmentShaderCode =
+    {
+      "INPUT mediump vec2       vTexCoord;\n\
+    INPUT mediump vec4       vColor;\n\
+    uniform sampler2D sTexture;\n\
+    \n\
+    void main()\n\
+    {\n\
+      lowp vec4 col = TEXTURE(sTexture, vTexCoord) * vColor;\n\
+      if(col.a < 0.1) { discard; }\
+      fragColor = col;\n\
+    }\n"};
+
+  mShader   = Shader::New(Dali::Shader::GetVertexShaderPrefix() + vertexShaderCode, Dali::Shader::GetFragmentShaderPrefix() + fragmentShaderCode);
+  mGeometry = Geometry::New();
+
+  // Configure geometry attributes
+  Property::Map geometryMap;
+  geometryMap.Add("aPosition", Dali::Property::VECTOR2);
+  geometryMap.Add("aTexCoords", Dali::Property::VECTOR2);
+
+  // One vertex buffer with geometry
+  VertexBuffer vertexBuffer0 = VertexBuffer::New(geometryMap);
+
+  // fill the buffer entirely
+  // 2D quad
+  const static Vector2 C(0.5f, 0.5f);
+  struct Quad2D
+  {
+    Vertex2D a0{Vector2(0.0f, 0.0f) - C, Vector2(0.0f, 0.0f)};
+    Vertex2D a1{Vector2(1.0f, 0.0f) - C, Vector2(1.0f, 0.0f)};
+    Vertex2D a2{Vector2(1.0f, 1.0f) - C, Vector2(1.0f, 1.0f)};
+    Vertex2D a3{Vector2(0.0f, 0.0f) - C, Vector2(0.0f, 0.0f)};
+    Vertex2D a4{Vector2(1.0f, 1.0f) - C, Vector2(1.0f, 1.0f)};
+    Vertex2D a5{Vector2(0.0f, 1.0f) - C, Vector2(0.0f, 1.0f)};
+  } QUAD;
+
+  std::vector<Quad2D> quads;
+  quads.resize(mEmitter->GetParticleList().GetCapacity());
+  std::fill(quads.begin(), quads.end(), QUAD);
+  vertexBuffer0.SetData(quads.data(), 6u * quads.size());
+
+  // Second vertex buffer with stream data
+  VertexBuffer vertexBuffer1 = VertexBuffer::New(streamAtttributes);
+
+  /**
+   * For more efficient stream management we need to support glVertexAttribDivisor() function.
+   * This will allow step 1 attribute per 4 vertices (GLES3+). Problem: DALi doesn't support instancing
+   *
+   * For older GLES2 we need to duplicate stream data (4x more memory in case of using a quad geometry)
+   *
+   * Point-sprites may be of use in the future (problem: point sprites use screen space)
+   */
+
+  // Based on the particle system, populate buffer
+  mGeometry.AddVertexBuffer(vertexBuffer0);
+  mGeometry.AddVertexBuffer(vertexBuffer1);
+
+  mGeometry.SetType(Geometry::TRIANGLES);
+
+  mVertexBuffer = vertexBuffer0;
+  mStreamBuffer = vertexBuffer1;
+
+  // Set some initial data for streambuffer to force initialization
+  std::vector<uint8_t> data;
+  // Resize using only-non local streams
+  auto elementSize = mEmitter->GetParticleList().GetParticleDataSize(false);
+  data.resize(elementSize *
+              mEmitter->GetParticleList().GetCapacity() * 6u);
+  mStreamBuffer.SetData(data.data(), mEmitter->GetParticleList().GetCapacity() * 6u); // needed to initialize
+
+  // Sets up callback
+  mStreamBuffer.SetVertexBufferUpdateCallback(std::move(mStreamBufferUpdateCallback));
+
+  mRenderer = Renderer::New(mGeometry, mShader);
+
+  mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::CONTINUOUSLY);
+  // If no texture created, the substitute rect 2x2 texture will be used
+  if(!mTexture)
+  {
+    mTexture         = Texture::New(TextureType::TEXTURE_2D, Pixel::RGBA8888, 2u, 2u);
+    auto* pixelArray = new uint32_t[4]{
+      0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF};
+
+    auto pixelData = PixelData::New(reinterpret_cast<uint8_t*>(pixelArray), 16, 2, 2, Pixel::Format::RGBA8888, PixelData::DELETE_ARRAY);
+    mTexture.Upload(pixelData);
+  }
+
+  mTextureSet = TextureSet::New();
+  mTextureSet.SetTexture(0, mTexture);
+  // mTextureSet.SetSampler(0, Sampler());
+
+  mRenderer.SetTextures(mTextureSet);
+
+  // Attach renderer to the parent actor
+  mEmitter->GetActor().AddRenderer(mRenderer);
+
+  if(mBlendingMode == BlendingMode::SCREEN)
+  {
+    if(Dali::Capabilities::IsBlendEquationSupported(Dali::DevelBlendEquation::SCREEN))
+    {
+      mEmitter->GetActor().SetProperty(Dali::DevelActor::Property::BLEND_EQUATION, Dali::DevelBlendEquation::SCREEN);
+    }
+    else // Fallback to default
+    {
+      mRenderer.SetProperty(Renderer::Property::BLEND_EQUATION_RGB, BlendEquation::ADD);
+    }
+  }
+  else
+  {
+    mRenderer.SetProperty(Renderer::Property::BLEND_EQUATION_RGB, BlendEquation::ADD);
+  }
+}
+
+uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size)
+{
+  auto& list = GetImplementation(mEmitter->GetParticleList());
+
+  auto particleCount    = list.GetActiveParticleCount(); // active particle count
+  auto particleMaxCount = list.GetParticleCount();
+  if(!particleCount)
+  {
+    return 0;
+  }
+
+  auto streamCount = list.GetStreamCount();
+
+  auto elementSize = 0u; // elements size should be cached (it's also stride of buffer) (in bytes)
+  for(auto i = 0u; i < streamCount; ++i)
+  {
+    if(!list.IsStreamLocal(i))
+    {
+      elementSize += list.GetStreamDataTypeSize(i);
+    }
+  }
+
+  // Prepare source buffer (MUST BE OPTIMIZED TO AVOID ALLOCATING AND COPYING!!)
+  auto totalSize = particleMaxCount * elementSize * 6u;
+
+  // buffer sizes must match
+  if(totalSize != size)
+  {
+    // ASSERT here ?
+    return 0;
+  }
+
+  auto* dst = reinterpret_cast<uint8_t*>(streamData);
+
+  auto& particles = list.GetParticles();
+
+  // prepare worker threads
+  auto workerCount = GetThreadPool().GetWorkerCount();
+
+  // divide particles if over the threshold
+
+  [[maybe_unused]] bool runParallel = true;
+  if(!mEmitter->IsParallelProcessingEnabled() || particleCount < workerCount * 10) // don't run parallel if only a few particles to update
+  {
+    runParallel = false;
+  }
+  else
+  {
+    // Partial to handle
+    [[maybe_unused]] auto partialSize = (particleCount / workerCount);
+
+    struct UpdateTask
+    {
+      UpdateTask(Internal::ParticleRenderer& renderer, Internal::ParticleList& list, uint32_t particleStartIndex, uint32_t particleCount, void* basePtr)
+      : owner(renderer),
+        particleList(list),
+        startIndex(particleStartIndex),
+        count(particleCount),
+        ptr(reinterpret_cast<uint8_t*>(basePtr))
+      {
+      }
+
+      void Update()
+      {
+        // execute task
+        owner.UpdateParticlesTask(particleList, startIndex, count, ptr);
+      }
+
+      Internal::ParticleRenderer& owner;
+      Internal::ParticleList&     particleList;
+      uint32_t                    startIndex;
+      uint32_t                    count;
+      uint8_t*                    ptr;
+    };
+
+    std::vector<UpdateTask> tasks;
+    tasks.reserve(workerCount);
+    std::vector<Task> taskQueue;
+    auto              count = partialSize;
+
+    for(auto i = 0u; i < workerCount; ++i)
+    {
+      auto index = i * partialSize;
+      count      = partialSize;
+
+      // make sure there's no leftover particles!
+      if(i == workerCount - 1 && index + count < particleCount)
+      {
+        count = particleCount - index;
+      }
+
+      tasks.emplace_back(*this, list, index, count, streamData);
+      taskQueue.emplace_back([&t = tasks.back()](uint32_t threadId)
+                             { t.Update(); });
+    }
+
+    // Execute worker tasks
+    auto future = GetThreadPool().SubmitTasks(taskQueue, 0);
+    // wait to finish
+    future->Wait();
+  }
+
+  // less particles so run on a single thread
+  if(!runParallel)
+  {
+    for(auto& p : particles)
+    {
+      // without instancing we need to duplicate data 4 times per each quad
+      auto* particleDst = dst;
+      for(auto s = 0u; s < streamCount; ++s)
+      {
+        if(!list.IsStreamLocal(s))
+        {
+          // Pointer to stream value
+          auto* valuePtr = &p.GetByIndex<uint8_t*>(s);
+
+          // Size of data
+          auto dataSize = list.GetStreamDataTypeSize(s);
+
+          memcpy(dst, valuePtr, dataSize);
+          dst += dataSize;
+        }
+      }
+      // Replicate data 5 more times for each vertex (GLES2)
+      memcpy(dst, particleDst, elementSize);
+      dst += elementSize;
+      memcpy(dst, particleDst, elementSize);
+      dst += elementSize;
+      memcpy(dst, particleDst, elementSize);
+      dst += elementSize;
+      memcpy(dst, particleDst, elementSize);
+      dst += elementSize;
+      memcpy(dst, particleDst, elementSize);
+      dst += elementSize;
+    }
+  }
+  return particleCount * 6u; // return number of elements to render
+}
+
+Renderer ParticleRenderer::GetRenderer() const
+{
+  return mRenderer;
+}
+
+void ParticleRenderer::UpdateParticlesTask(Internal::ParticleList& list,
+                                           uint32_t                particleStartIndex,
+                                           uint32_t                particleCount,
+                                           uint8_t*                basePtr)
+{
+  auto& particles   = list.GetParticles();
+  auto  streamCount = list.GetStreamCount();
+  auto  elementSize = list.GetStreamElementSize(false);
+
+  // calculate begin of buffer
+  uint8_t* dst = (basePtr + (elementSize * 6u) * particleStartIndex);
+
+  auto it = particles.begin();
+  std::advance(it, particleStartIndex);
+
+  for(; particleCount; particleCount--, it++)
+  {
+    ParticleSystem::Particle& p = *it;
+    // without instancing we need to duplicate data 4 times per each quad
+    auto* particleDst = dst;
+    for(auto s = 0u; s < streamCount; ++s)
+    {
+      if(!list.IsStreamLocal(s))
+      {
+        // Pointer to stream value
+        auto* valuePtr = &p.GetByIndex<uint8_t*>(s);
+
+        // Size of data
+        auto dataSize = list.GetStreamDataTypeSize(s);
+
+        memcpy(dst, valuePtr, dataSize);
+        dst += dataSize;
+      }
+    }
+    // Replicate data 5 more times for each vertex (GLES2)
+    memcpy(dst, particleDst, elementSize);
+    dst += elementSize;
+    memcpy(dst, particleDst, elementSize);
+    dst += elementSize;
+    memcpy(dst, particleDst, elementSize);
+    dst += elementSize;
+    memcpy(dst, particleDst, elementSize);
+    dst += elementSize;
+    memcpy(dst, particleDst, elementSize);
+    dst += elementSize;
+  }
+}
+
+bool ParticleRenderer::Initialize()
+{
+  if(!mInitialized)
+  {
+    CreateShader();
+    mInitialized = true;
+    return true;
+  }
+
+  return false;
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-renderer-impl.h b/dali-toolkit/internal/particle-system/particle-renderer-impl.h
new file mode 100644 (file)
index 0000000..bcdb262
--- /dev/null
@@ -0,0 +1,116 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_RENDERER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_RENDERER_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.
+ *
+ */
+
+#include <memory>
+
+#include <dali-toolkit/public-api/particle-system/particle-emitter.h>
+#include <dali-toolkit/public-api/particle-system/particle-renderer.h>
+#include <dali-toolkit/public-api/particle-system/particle.h>
+
+#include <dali/public-api/actors/actor.h>
+#include <dali/public-api/object/base-object.h>
+#include <dali/public-api/rendering/renderer.h>
+#include <dali/public-api/rendering/vertex-buffer.h>
+#include <dali/public-api/signals/render-callback.h>
+
+namespace Dali::Graphics
+{
+class Controller;
+class Shader;
+} // namespace Dali::Graphics
+
+namespace Dali::Toolkit::ParticleSystem
+{
+class ParticleEmitter;
+}
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleEmitter;
+class ParticleRenderer;
+class ParticleList;
+
+class ParticleRenderer : public Dali::BaseObject
+{
+public:
+  ParticleRenderer();
+
+  /**
+   * Create shader
+   */
+  void CreateShader();
+
+  void SetTexture(const Dali::Texture& texture);
+
+  void SetBlendingMode(BlendingMode blendingMode);
+
+  bool Initialize();
+
+  void SetEmitter(ParticleSystem::Internal::ParticleEmitter* emitter);
+
+  void UpdateParticlesTask(Internal::ParticleList& list,
+                           uint32_t                particleStartIndex,
+                           uint32_t                particleCount,
+                           uint8_t*                dst);
+
+  [[nodiscard]] Renderer GetRenderer() const;
+
+  uint32_t OnStreamBufferUpdate(void* data, size_t size);
+
+  bool mUsingStreamDivisor{true}; ///< If attribute divisor is supported, it's going to be used
+
+  Internal::ParticleEmitter* mEmitter{nullptr}; ///< Emitter implementation that uses the renderer
+
+  Dali::Texture      mTexture;
+  Dali::TextureSet   mTextureSet;
+  Dali::Renderer     mRenderer;
+  Dali::Shader       mShader;
+  Dali::Geometry     mGeometry;
+  Dali::VertexBuffer mVertexBuffer;
+  Dali::VertexBuffer mStreamBuffer;
+  BlendingMode       mBlendingMode{BlendingMode::DEFAULT};
+
+  std::unique_ptr<Dali::VertexBufferUpdateCallback> mStreamBufferUpdateCallback;
+
+  bool mInitialized{false};
+};
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+namespace Dali
+{
+inline Toolkit::ParticleSystem::Internal::ParticleRenderer& GetImplementation(Toolkit::ParticleSystem::ParticleRenderer& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleRenderer handle is empty");
+
+  BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<Toolkit::ParticleSystem::Internal::ParticleRenderer&>(handle);
+}
+
+inline const Toolkit::ParticleSystem::Internal::ParticleRenderer& GetImplementation(const Toolkit::ParticleSystem::ParticleRenderer& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleRenderer handle is empty");
+
+  const BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<const Toolkit::ParticleSystem::Internal::ParticleRenderer&>(handle);
+}
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_RENDERER_H
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-source-impl.cpp b/dali-toolkit/internal/particle-system/particle-source-impl.cpp
new file mode 100644 (file)
index 0000000..3d5c0a3
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-emitter-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-source-impl.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+ParticleSource::ParticleSource(std::unique_ptr<ParticleSourceInterface>&& sourceUpdater)
+: mUpdater(std::move(sourceUpdater))
+{
+}
+
+void ParticleSource::Update(ParticleSystem::ParticleList& list, uint32_t count)
+{
+  // updating the source
+  mUpdater->Update(list, count);
+}
+
+ParticleSourceInterface& ParticleSource::GetUpdater() const
+{
+  return *mUpdater;
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
\ No newline at end of file
diff --git a/dali-toolkit/internal/particle-system/particle-source-impl.h b/dali-toolkit/internal/particle-system/particle-source-impl.h
new file mode 100644 (file)
index 0000000..21692ac
--- /dev/null
@@ -0,0 +1,67 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_SOURCE_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_SOURCE_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.
+ *
+ */
+
+#include <dali-toolkit/public-api/particle-system/particle-source.h>
+#include <dali/public-api/object/base-object.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleList;
+class ParticleEmitter;
+class ParticleSource : public Dali::BaseObject
+{
+public:
+  ~ParticleSource() override = default;
+
+  explicit ParticleSource(std::unique_ptr<ParticleSourceInterface>&& sourceUpdater);
+
+  void Update(ParticleSystem::ParticleList& list, uint32_t count);
+
+  [[nodiscard]] ParticleSourceInterface& GetUpdater() const;
+
+private:
+  std::unique_ptr<ParticleSourceInterface> mUpdater;
+};
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+
+namespace Dali::Toolkit::ParticleSystem
+{
+inline Internal::ParticleSource& GetImplementation(ParticleSystem::ParticleSource& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleSource handle is empty");
+
+  BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<Internal::ParticleSource&>(handle);
+}
+
+inline const Internal::ParticleSource& GetImplementation(const ParticleSystem::ParticleSource& source)
+{
+  DALI_ASSERT_ALWAYS(source && "ParticleSource handle is empty");
+
+  const BaseObject& handle = source.GetBaseObject();
+
+  return static_cast<const Internal::ParticleSource&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_SOURCE_H
\ No newline at end of file
index f7bd90a..e280739 100644 (file)
@@ -859,22 +859,10 @@ InputMethodContext::CallbackData Controller::EventHandler::OnInputMethodContextE
 
   std::string    text;
   CharacterIndex cursorPosition      = 0u;
-  Length         numberOfWhiteSpaces = 0u;
 
   if(retrieveCursor)
   {
-    numberOfWhiteSpaces = controller.mImpl->GetNumberOfWhiteSpaces(0u);
-
     cursorPosition = controller.mImpl->GetLogicalCursorPosition();
-
-    if(cursorPosition < numberOfWhiteSpaces)
-    {
-      cursorPosition = 0u;
-    }
-    else
-    {
-      cursorPosition -= numberOfWhiteSpaces;
-    }
   }
 
   if(retrieveText)
@@ -882,7 +870,7 @@ InputMethodContext::CallbackData Controller::EventHandler::OnInputMethodContextE
     if(!controller.mImpl->IsShowingPlaceholderText())
     {
       // Retrieves the normal text string.
-      controller.mImpl->GetText(numberOfWhiteSpaces, text);
+      controller.mImpl->GetText(0u, text);
     }
     else
     {
index 66d8498..cc38fd0 100644 (file)
@@ -444,19 +444,6 @@ void Controller::Impl::NotifyInputMethodContext()
   if(mEventData && mEventData->mInputMethodContext)
   {
     CharacterIndex cursorPosition = GetLogicalCursorPosition();
-
-    const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u);
-
-    // Update the cursor position by removing the initial white spaces.
-    if(cursorPosition < numberOfWhiteSpaces)
-    {
-      cursorPosition = 0u;
-    }
-    else
-    {
-      cursorPosition -= numberOfWhiteSpaces;
-    }
-
     mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
     mEventData->mInputMethodContext.NotifyCursorPosition();
   }
index 8d95497..aa0a062 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 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.
  *
  */
 
-// EXTERNAL INCLUDES
+// CLASS HEADER
 #include <dali-toolkit/internal/text/spannable/span-ranges-container-impl.h>
-#include <map>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/common/map-wrapper.h>
+#include <utility>
 
 namespace Dali
 {
@@ -30,7 +33,13 @@ namespace Internal
 {
 struct SpanRangesContainer::Impl
 {
-  std::map<Dali::Toolkit::Text::BaseSpan, Dali::Toolkit::Text::Range> mSpanWithRanges; ///< The list of style-span
+  using SpanId = uint32_t;
+
+  // Key : BaseSpan, Value : <Range, Added order>
+  using SpanRangeContainer = std::map<Dali::Toolkit::Text::BaseSpan, std::pair<Dali::Toolkit::Text::Range, SpanId>>;
+
+  SpanRangeContainer mSpanWithRanges{}; ///< The list of style-span
+  SpanId             mSpanId{0};        ///< The global id for each added Span. It will be used when we determine order of span.
 };
 
 SpanRangesContainer::SpanRangesContainer()
@@ -44,7 +53,7 @@ SpanRangesContainer::~SpanRangesContainer()
 
 void SpanRangesContainer::AddSpan(const Dali::Toolkit::Text::BaseSpan& span, const Dali::Toolkit::Text::Range& range)
 {
-  mImpl->mSpanWithRanges.insert(std::make_pair(span, range));
+  mImpl->mSpanWithRanges.insert(std::make_pair(span, std::make_pair(range, mImpl->mSpanId++)));
 }
 
 void SpanRangesContainer::RemoveSpan(const Dali::Toolkit::Text::BaseSpan& span)
@@ -54,29 +63,48 @@ void SpanRangesContainer::RemoveSpan(const Dali::Toolkit::Text::BaseSpan& span)
 
 bool SpanRangesContainer::Contains(const Dali::Toolkit::Text::BaseSpan& span) const
 {
-  std::map<Dali::Toolkit::Text::BaseSpan, Dali::Toolkit::Text::Range>::iterator it = mImpl->mSpanWithRanges.find(span);
+  const auto it = mImpl->mSpanWithRanges.find(span);
 
   return it != mImpl->mSpanWithRanges.end();
 }
 
 void SpanRangesContainer::GetSpans(std::vector<Dali::Toolkit::Text::BaseSpan>& listOfSpans) const
 {
-  for(std::map<Dali::Toolkit::Text::BaseSpan, Dali::Toolkit::Text::Range>::iterator it = mImpl->mSpanWithRanges.begin();
-      it != mImpl->mSpanWithRanges.end();
-      ++it)
+  std::map<Impl::SpanId, Dali::Toolkit::Text::BaseSpan> reorderedListOfSpans;
+
+  listOfSpans.reserve(listOfSpans.size() + mImpl->mSpanWithRanges.size());
+
+  // At first, sort the list of spans ordered by added time.
+  for(auto iter = mImpl->mSpanWithRanges.begin(), iterEnd = mImpl->mSpanWithRanges.end(); iter != iterEnd; ++iter)
   {
-    listOfSpans.push_back(it->first);
+    reorderedListOfSpans.insert(std::make_pair(iter->second.second, iter->first));
+  }
+
+  // Retrieve result.
+  for(auto iter = reorderedListOfSpans.begin(), iterEnd = reorderedListOfSpans.end(); iter != iterEnd; ++iter)
+  {
+    listOfSpans.push_back(iter->second);
   }
 }
 
 void SpanRangesContainer::GetSpansAndRanges(std::vector<Dali::Toolkit::Text::BaseSpan>& spans, std::vector<Dali::Toolkit::Text::Range>& ranges) const
 {
-  for(std::map<Dali::Toolkit::Text::BaseSpan, Dali::Toolkit::Text::Range>::iterator it = mImpl->mSpanWithRanges.begin();
-      it != mImpl->mSpanWithRanges.end();
-      ++it)
+  std::map<Impl::SpanId, std::pair<Dali::Toolkit::Text::BaseSpan, Dali::Toolkit::Text::Range>> reorderedListOfSpansWithRanges;
+
+  spans.reserve(spans.size() + mImpl->mSpanWithRanges.size());
+  ranges.reserve(ranges.size() + mImpl->mSpanWithRanges.size());
+
+  // At first, sort the list of spans ordered by added time.
+  for(auto iter = mImpl->mSpanWithRanges.begin(), iterEnd = mImpl->mSpanWithRanges.end(); iter != iterEnd; ++iter)
+  {
+    reorderedListOfSpansWithRanges.insert(std::make_pair(iter->second.second, std::make_pair(iter->first, iter->second.first)));
+  }
+
+  // Retrieve result.
+  for(auto iter = reorderedListOfSpansWithRanges.begin(), iterEnd = reorderedListOfSpansWithRanges.end(); iter != iterEnd; ++iter)
   {
-    spans.push_back(it->first);
-    ranges.push_back(it->second);
+    spans.push_back(iter->second.first);
+    ranges.push_back(iter->second.second);
   }
 }
 } // namespace Internal
index 4666cc5..74463da 100644 (file)
@@ -318,7 +318,25 @@ TextureSet TextureManager::LoadTexture(
           // TODO : Should we seperate input and output value?
           preMultiplyOnLoad = externalTextureInfo.preMultiplied ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
         }
-        return externalTextureInfo.textureSet;
+
+        TextureId alphaMaskId = INVALID_TEXTURE_ID;
+        if(maskInfo && maskInfo->mAlphaMaskUrl.IsValid())
+        {
+          maskInfo->mAlphaMaskId = RequestMaskLoad(maskInfo->mAlphaMaskUrl, StorageType::KEEP_TEXTURE, synchronousLoading);
+          alphaMaskId            = maskInfo->mAlphaMaskId;
+          textureId              = RequestLoad(url, alphaMaskId, 1.0f, desiredSize, fittingMode, samplingMode, UseAtlas::NO_ATLAS, false, textureObserver, orientationCorrection, reloadPolicy, preMultiplyOnLoad, synchronousLoading);
+
+          TextureManager::LoadState loadState = mTextureCacheManager.GetTextureStateInternal(textureId);
+          if(loadState == TextureManager::LoadState::UPLOADED)
+          {
+            textureSet = GetTextureSet(textureId);
+          }
+        }
+        else
+        {
+          textureSet = TextureSet::New();
+          textureSet.SetTexture(TEXTURE_INDEX, externalTextureInfo.textureSet.GetTexture(TEXTURE_INDEX));
+        }
       }
     }
   }
@@ -566,6 +584,15 @@ TextureManager::TextureId TextureManager::RequestLoadInternal(
   textureInfo.storageType           = storageType;
   textureInfo.orientationCorrection = orientationCorrection;
 
+  // the case using external texture has already been loaded texture, so change its status to WAITING_FOR_MASK.
+  if(url.GetProtocolType() == VisualUrl::TEXTURE)
+  {
+    if(textureInfo.loadState != LoadState::UPLOADED)
+    {
+      textureInfo.loadState = TextureManager::LoadState::WAITING_FOR_MASK;
+    }
+  }
+
   DALI_LOG_INFO(gTextureManagerLogFilter, Debug::General, "TextureInfo loadState:%s\n", GET_LOAD_STATE_STRING(textureInfo.loadState));
 
   // Force reloading of texture by setting loadState unless already loading or cancelled.
@@ -643,58 +670,66 @@ TextureManager::TextureId TextureManager::RequestLoadInternal(
     if(!(textureInfo.loadState == TextureManager::LoadState::UPLOADED ||
          textureInfo.loadState == TextureManager::LoadState::LOAD_FINISHED))
     {
-      std::vector<Devel::PixelBuffer> pixelBuffers;
-      LoadImageSynchronously(url, desiredSize, fittingMode, samplingMode, orientationCorrection, loadYuvPlanes, pixelBuffers);
-
-      if(pixelBuffers.empty())
+      if(url.GetProtocolType() == VisualUrl::TEXTURE)
       {
-        // If pixelBuffer loading is failed in synchronously, call RequestRemove() method.
-        RequestRemove(textureId, nullptr);
-        return INVALID_TEXTURE_ID;
-      }
-
-      if(storageType == StorageType::KEEP_PIXEL_BUFFER) // For the mask image loading.
-      {
-        textureInfo.pixelBuffer = pixelBuffers[0]; // Store the pixel data
-        textureInfo.loadState   = LoadState::LOAD_FINISHED;
+        // Get external textureSet from cacheManager.
+        std::string location = textureInfo.url.GetLocation();
+        if(!location.empty())
+        {
+          TextureId id                  = std::stoi(location);
+          auto      externalTextureInfo = mTextureCacheManager.GetExternalTextureInfo(id);
+          textureInfo.textures.push_back(externalTextureInfo.textureSet.GetTexture(0));
+          textureInfo.loadState = LoadState::UPLOADED;
+        }
       }
-      else // For the image loading.
+      else
       {
-        Texture maskTexture;
-        if(maskTextureId != INVALID_TEXTURE_ID)
+        std::vector<Devel::PixelBuffer> pixelBuffers;
+        LoadImageSynchronously(url, desiredSize, fittingMode, samplingMode, orientationCorrection, loadYuvPlanes, pixelBuffers);
+
+        if(pixelBuffers.empty())
         {
-          TextureCacheIndex maskCacheIndex = mTextureCacheManager.GetCacheIndexFromId(maskTextureId);
-          if(maskCacheIndex != INVALID_CACHE_INDEX)
+          // If pixelBuffer loading is failed in synchronously, call RequestRemove() method.
+          RequestRemove(textureId, nullptr);
+          return INVALID_TEXTURE_ID;
+        }
+
+        if(storageType == StorageType::KEEP_PIXEL_BUFFER) // For the mask image loading.
+        {
+          textureInfo.pixelBuffer = pixelBuffers[0]; // Store the pixel data
+          textureInfo.loadState   = LoadState::LOAD_FINISHED;
+        }
+        else // For the image loading.
+        {
+          Texture maskTexture;
+          if(maskTextureId != INVALID_TEXTURE_ID)
           {
-            if(mTextureCacheManager[maskCacheIndex].storageType == StorageType::KEEP_TEXTURE)
+            TextureCacheIndex maskCacheIndex = mTextureCacheManager.GetCacheIndexFromId(maskTextureId);
+            if(maskCacheIndex != INVALID_CACHE_INDEX)
             {
-              if(!mTextureCacheManager[maskCacheIndex].textures.empty())
+              if(mTextureCacheManager[maskCacheIndex].storageType == StorageType::KEEP_PIXEL_BUFFER)
               {
-                maskTexture = mTextureCacheManager[maskCacheIndex].textures[0];
+                Devel::PixelBuffer maskPixelBuffer = mTextureCacheManager[maskCacheIndex].pixelBuffer;
+                if(maskPixelBuffer)
+                {
+                  pixelBuffers[0].ApplyMask(maskPixelBuffer, contentScale, cropToMask);
+                }
+                else
+                {
+                  DALI_LOG_ERROR("Mask image cached invalid pixel buffer!\n");
+                }
               }
             }
-            else if(mTextureCacheManager[maskCacheIndex].storageType == StorageType::KEEP_PIXEL_BUFFER)
+            else
             {
-              Devel::PixelBuffer maskPixelBuffer = mTextureCacheManager[maskCacheIndex].pixelBuffer;
-              if(maskPixelBuffer)
-              {
-                pixelBuffers[0].ApplyMask(maskPixelBuffer, contentScale, cropToMask);
-              }
-              else
-              {
-                DALI_LOG_ERROR("Mask image cached invalid pixel buffer!\n");
-              }
+              DALI_LOG_ERROR("Mask image is not stored in cache.\n");
             }
           }
-          else
-          {
-            DALI_LOG_ERROR("Mask image is not stored in cache.\n");
-          }
-        }
-        PreMultiply(pixelBuffers[0], preMultiplyOnLoad);
+          PreMultiply(pixelBuffers[0], preMultiplyOnLoad);
 
-        // Upload texture
-        UploadTextures(pixelBuffers, textureInfo);
+          // Upload texture
+          UploadTextures(pixelBuffers, textureInfo);
+        }
       }
     }
   }
@@ -1153,10 +1188,25 @@ void TextureManager::CheckForWaitingTexture(TextureManager::TextureInfo& maskTex
       {
         if(maskTextureInfo.storageType == StorageType::KEEP_TEXTURE)
         {
-          // Upload image texture. textureInfo.loadState will be UPLOADED.
-          std::vector<Devel::PixelBuffer> pixelBuffers;
-          pixelBuffers.push_back(textureInfo.pixelBuffer);
-          UploadTextures(pixelBuffers, textureInfo);
+          if(textureInfo.url.GetProtocolType() == VisualUrl::TEXTURE)
+          {
+            // Get external textureSet from cacheManager.
+            std::string location = textureInfo.url.GetLocation();
+            if(!location.empty())
+            {
+              TextureId id                  = std::stoi(location);
+              auto      externalTextureInfo = mTextureCacheManager.GetExternalTextureInfo(id);
+              textureInfo.textures.push_back(externalTextureInfo.textureSet.GetTexture(0));
+              textureInfo.loadState = LoadState::UPLOADED;
+            }
+          }
+          else
+          {
+            // Upload image texture. textureInfo.loadState will be UPLOADED.
+            std::vector<Devel::PixelBuffer> pixelBuffers;
+            pixelBuffers.push_back(textureInfo.pixelBuffer);
+            UploadTextures(pixelBuffers, textureInfo);
+          }
 
           // Increase reference counts for notify required textureId.
           // Now we can assume that we don't remove & re-assign this textureId
index 1263fbd..5d987b9 100644 (file)
@@ -294,6 +294,17 @@ void AnimatedVectorImageVisual::DoSetProperty(Property::Index index, const Prope
         mAnimationData.playRange = *array;
         mAnimationData.resendFlag |= VectorAnimationTask::RESEND_PLAY_RANGE;
       }
+      else if(value.GetType() == Property::STRING)
+      {
+        std::string markerName;
+        if(value.Get(markerName))
+        {
+          Property::Array array;
+          array.Add(markerName);
+          mAnimationData.playRange = std::move(array);
+          mAnimationData.resendFlag |= VectorAnimationTask::RESEND_PLAY_RANGE;
+        }
+      }
       break;
     }
     case Toolkit::DevelImageVisual::Property::STOP_BEHAVIOR:
index 2f51b7a..8adcb32 100644 (file)
@@ -424,7 +424,16 @@ void ImageVisual::DoSetProperty(Property::Index index, const Property::Value& va
       if(value.Get(maskingType))
       {
         AllocateMaskData();
-        mMaskingData->mPreappliedMasking = Toolkit::DevelImageVisual::MaskingType::Type(maskingType) == Toolkit::DevelImageVisual::MaskingType::MASKING_ON_LOADING ? true : false;
+        if(mImageUrl.IsValid() && mImageUrl.GetProtocolType() == VisualUrl::TEXTURE)
+        {
+          // For external textures, only gpu masking is available.
+          // Therefore, MASKING_TYPE is set to MASKING_ON_RENDERING forcelly.
+          mMaskingData->mPreappliedMasking = false;
+        }
+        else
+        {
+          mMaskingData->mPreappliedMasking = Toolkit::DevelImageVisual::MaskingType::Type(maskingType) == Toolkit::DevelImageVisual::MaskingType::MASKING_ON_LOADING ? true : false;
+        }
       }
       break;
     }
@@ -461,6 +470,10 @@ void ImageVisual::AllocateMaskData()
   if(!mMaskingData)
   {
     mMaskingData.reset(new TextureManager::MaskingData());
+    if(mImageUrl.IsValid() && mImageUrl.GetProtocolType() == VisualUrl::TEXTURE)
+    {
+      mMaskingData->mPreappliedMasking = false;
+    }
   }
 }
 
index a5144f6..8fa5ed8 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 = 24;
+const unsigned int TOOLKIT_MICRO_VERSION = 26;
 const char* const  TOOLKIT_BUILD_DATE    = __DATE__ " " __TIME__;
 
 #ifdef DEBUG_ENABLED
index ffd306b..833bfbf 100644 (file)
@@ -32,6 +32,13 @@ SET( public_api_src_files
   ${public_api_src_dir}/image-loader/image-url.cpp
   ${public_api_src_dir}/image-loader/async-image-loader.cpp
   ${public_api_src_dir}/image-loader/sync-image-loader.cpp
+  ${public_api_src_dir}/particle-system/particle.cpp
+  ${public_api_src_dir}/particle-system/particle-domain.cpp
+  ${public_api_src_dir}/particle-system/particle-emitter.cpp
+  ${public_api_src_dir}/particle-system/particle-list.cpp
+  ${public_api_src_dir}/particle-system/particle-modifier.cpp
+  ${public_api_src_dir}/particle-system/particle-renderer.cpp
+  ${public_api_src_dir}/particle-system/particle-source.cpp
   ${public_api_src_dir}/styling/style-manager.cpp
   ${public_api_src_dir}/transition/fade-transition.cpp
   ${public_api_src_dir}/transition/slide-transition.cpp
@@ -157,6 +164,17 @@ SET( public_api_visuals_header_files
   ${public_api_src_dir}/visuals/text-visual-properties.h
 )
 
+SET( public_api_particle_system_header_files
+  ${public_api_src_dir}/particle-system/particle-domain.h
+  ${public_api_src_dir}/particle-system/particle-emitter.h
+  ${public_api_src_dir}/particle-system/particle.h
+  ${public_api_src_dir}/particle-system/particle-list.h
+  ${public_api_src_dir}/particle-system/particle-modifier.h
+  ${public_api_src_dir}/particle-system/particle-renderer.h
+  ${public_api_src_dir}/particle-system/particle-source.h
+  ${public_api_src_dir}/particle-system/particle-types.h
+)
+
 SET( public_api_transition_header_files
   ${public_api_src_dir}/transition/fade-transition.h
   ${public_api_src_dir}/transition/slide-transition.h
@@ -192,4 +210,5 @@ SET( PUBLIC_API_HEADERS ${PUBLIC_API_HEADERS}
   ${public_api_visuals_header_files}
   ${public_api_camera_view_header_files}
   ${public_api_gl_view_header_files}
+  ${public_api_particle_system_header_files}
 )
diff --git a/dali-toolkit/public-api/particle-system/particle-domain.cpp b/dali-toolkit/public-api/particle-system/particle-domain.cpp
new file mode 100644 (file)
index 0000000..135926a
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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-toolkit/public-api/particle-system/particle-domain.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/particle-system/particle-domain-impl.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+ParticleDomain ParticleDomain::New()
+{
+  return {new Internal::ParticleDomain()};
+}
+
+ParticleDomain::ParticleDomain(Internal::ParticleDomain* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+ParticleDomain ParticleDomain::DownCast(BaseHandle handle)
+{
+  return {dynamic_cast<Internal::ParticleDomain*>(handle.GetObjectPtr())};
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
diff --git a/dali-toolkit/public-api/particle-system/particle-domain.h b/dali-toolkit/public-api/particle-system/particle-domain.h
new file mode 100644 (file)
index 0000000..b7b103d
--- /dev/null
@@ -0,0 +1,79 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_DOMAIN_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_DOMAIN_H
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleDomain;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+
+/**
+ * @class ParticleDomain
+ *
+ * @brief ParticleDomain bounds simulation area to the specified region
+ */
+class DALI_TOOLKIT_API ParticleDomain : public Dali::BaseHandle
+{
+public:
+  /**
+   * @brief Constructor
+   */
+  ParticleDomain() = default;
+
+  /**
+   * @brief Creates new ParticleDomain object
+   *
+   * @return New ParticleDomain object
+   */
+  static ParticleDomain New();
+
+  /**
+   * @brief Downcasts a handle to ParticleDomain handle.
+   *
+   * If handle points to an ParticleDomain object, the downcast produces valid handle.
+   * If not, the returned handle is left uninitialized.
+   *
+   * @param[in] handle to An object
+   * @return handle to a ParticleDomain object or an uninitialized handle
+   */
+  static ParticleDomain DownCast(BaseHandle handle);
+
+private:
+  /// @cond internal
+  /**
+   * @brief This constructor is used by ParticleDomain::New() methods.
+   *
+   * @param [in] impl A pointer to a newly allocated implementation
+   */
+  ParticleDomain(Internal::ParticleDomain* impl);
+  /// @endcond
+};
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_DOMAIN_H
\ No newline at end of file
diff --git a/dali-toolkit/public-api/particle-system/particle-emitter.cpp b/dali-toolkit/public-api/particle-system/particle-emitter.cpp
new file mode 100644 (file)
index 0000000..6a3e746
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * 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-toolkit/public-api/particle-system/particle-emitter.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-source.h>
+#include <dali-toolkit/internal/particle-system/particle-emitter-impl.h>
+
+// EXTERNAL INCLUDES
+#include <utility>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+
+ParticleEmitter ParticleEmitter::New()
+{
+  return {new Dali::Toolkit::ParticleSystem::Internal::ParticleEmitter()};
+}
+
+ParticleEmitter ParticleEmitter::DownCast(BaseHandle handle)
+{
+  return {dynamic_cast<Internal::ParticleEmitter*>(handle.GetObjectPtr())};
+}
+
+void ParticleEmitter::SetSource(const ParticleSource& particleSource)
+{
+  GetImplementation(*this).SetSource(particleSource);
+}
+
+ParticleSource ParticleEmitter::GetSource() const
+{
+  return GetImplementation(*this).GetSource();
+}
+
+void ParticleEmitter::SetDomain(const ParticleDomain& particleDomain)
+{
+  GetImplementation(*this).SetDomain(particleDomain);
+}
+
+void ParticleEmitter::SetRenderer(const ParticleRenderer& particleRenderer)
+{
+  GetImplementation(*this).SetRenderer(particleRenderer);
+}
+
+void ParticleEmitter::SetParticleCount(uint32_t maxParticleCount)
+{
+  GetImplementation(*this).SetParticleCount(maxParticleCount);
+}
+
+ParticleDomain ParticleEmitter::GetDomain() const
+{
+  return GetImplementation(*this).GetDomain();
+}
+
+ParticleRenderer ParticleEmitter::GetRenderer() const
+{
+  return GetImplementation(*this).GetRenderer();
+}
+
+uint32_t ParticleEmitter::AddModifier(const ParticleModifier& particleModifier)
+{
+  return GetImplementation(*this).AddModifier(particleModifier);
+}
+
+ParticleModifier ParticleEmitter::GetModifierAt(uint32_t index)
+{
+  return GetImplementation(*this).GetModifierAt(index);
+}
+
+void ParticleEmitter::RemoveModifierAt(uint32_t index)
+{
+  return GetImplementation(*this).RemoveModifierAt(index);
+}
+
+void ParticleEmitter::AttachTo(Actor actor)
+{
+  GetImplementation(*this).AttachTo(std::move(actor));
+}
+
+ParticleEmitter::ParticleEmitter(Internal::ParticleEmitter* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+ParticleEmitter::Status ParticleEmitter::GetStatus() const
+{
+  return GetImplementation(*this).GetStatus();
+}
+
+ParticleList& ParticleEmitter::GetParticleList()
+{
+  return GetImplementation(*this).GetParticleList();
+}
+
+void ParticleEmitter::Start()
+{
+  GetImplementation(*this).Start();
+}
+
+void ParticleEmitter::Stop()
+{
+  GetImplementation(*this).Stop();
+}
+
+void ParticleEmitter::SetEmissionRate(uint32_t ratePerSecond)
+{
+  GetImplementation(*this).SetEmissionRate(ratePerSecond);
+}
+
+uint32_t ParticleEmitter::GetEmissionRate() const
+{
+  return GetImplementation(*this).GetEmissionRate();
+}
+
+[[maybe_unused]] void ParticleEmitter::EnableParallelProcessing(bool enabled)
+{
+  GetImplementation(*this).EnableParallelProcessing(enabled);
+}
+
+bool ParticleEmitter::IsParallelProcessingEnabled() const
+{
+  return GetImplementation(*this).IsParallelProcessingEnabled();
+}
+
+void ParticleEmitter::SetInitialParticleCount(uint32_t count)
+{
+  GetImplementation(*this).SetInitialParticleCount(count);
+}
+
+uint32_t ParticleEmitter::GetInitialParticleCount() const
+{
+  return GetImplementation(*this).GetInitialParticleCount();
+}
+
+void ParticleEmitter::SetActiveParticlesLimit(uint32_t count)
+{
+  GetImplementation(*this).SetActiveParticlesLimit(count);
+}
+
+uint32_t ParticleEmitter::GetActiveParticlesLimit() const
+{
+  return GetImplementation(*this).GetActiveParticlesLimit();
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
diff --git a/dali-toolkit/public-api/particle-system/particle-emitter.h b/dali-toolkit/public-api/particle-system/particle-emitter.h
new file mode 100644 (file)
index 0000000..15e3aa7
--- /dev/null
@@ -0,0 +1,337 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_EMITTER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_EMITTER_H
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/actors/actor.h>
+#include <dali/public-api/object/base-handle.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleEmitter;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+class ParticleSource;
+
+class ParticleDomain;
+
+class ParticleList;
+
+class ParticleModifier;
+
+class ParticleRenderer;
+
+/**
+ * @class ParticleEmitter
+ *
+ * ParticleEmitter manages a particle emission process. The primary function
+ * of the particle emitter class is to emit particles into a simulated environment.
+ *
+ * ParticleEmitter is responsible for:
+
+ * - Particle generation:
+ *
+ * The emitter generates particles with specific initial properties,
+ * such as position, velocity, size etc. It can create particles various ways which can be
+ * implemented by overriding ParticleSourceInterface. It may create particles in bursts, streams
+ * or in response to specific events. The ParticleSource must be set in order to generate particles
+ * in the system.
+ *
+ * - Particle simulation:
+ *
+ * The emitter updates state of each particle by invoking ParticleModifier stack. It updates particles
+ * over time to simulate desired behaviour. Particle modifiers may apply modifications to the system like
+ * applying forces (gravity, wind), integrating physics.
+ *
+ * The stack of modifiers is executed in order and output of previous modifier is an input of
+ * next one.
+ *
+ * At least one ParticleModifier must be set in order to update particle system and run
+ * the simulation.
+ *
+ * - Particle rendering
+ *
+ * ParticleRenderer must be set in order to render particles. The basic renderer renders only 2D billboard
+ * projected (always facing the camera) particles however the behaviour can be altered in order to render
+ * more complex systems.
+ *
+ * Rendering may be optimize for different graphics APIs.
+ *
+ * - Particle management
+ *
+ * The emitter manages the lifecycle of particles, including creation (via ParticleSource),
+ * update (via ParticleModifier stack) and removal (modifiers and specified lifetime of particles). It
+ * handles scenarios such as recycling particles that have reached the end of their lifespan, reusing them,
+ * or dynamically adjusting their properties based on the emitter's parameters or external factors.
+ *
+ * The particles are stored as ParticleList object which is generated internally. The emitter controls
+ * behaviour of the particle list.
+ *
+ * The basic components making the Particle System are:
+ * - ParticleEmitter - responsible for controlling the emission
+ * - ParticleSource - responsible for generating new particles in the system
+ * - ParticleModifier - responsible for altering the behaviour of particles (updates) and controlling the lifetime
+ * - ParticleRenderer - responsible for rendering the particle system
+ * - ParticleList - storage for particle data
+ * - Particle - view on a selected particle data
+ * - ParticleDomain - the domain of particle system (area/volume) that particle system is bound within
+ *
+ */
+class DALI_TOOLKIT_API ParticleEmitter : public Dali::BaseHandle
+{
+public:
+  /**
+   * @brief Enum describing status of emitter
+   */
+  enum class Status
+  {
+    INCOMPLETE, ///< Status set when not all data is set in order to run simulation
+    READY,      ///< Emitter ready (fully set up)
+    STARTED,    ///< Emitter started
+    PAUSED,     ///< Emitter paused
+    STOPPED     ///< Emitter stopped
+  };
+
+  /**
+   * @brief Creates new ParticleEmitter
+   *
+   * Function creates new ParticleEmitter object with given initial specification.
+   *
+   * @return valid emitter object
+   */
+  static ParticleEmitter New();
+
+  /**
+   * @brief Downcasts a handle to ParticleEmitter handle.
+   *
+   * If handle points to an ParticleEmitter object, the downcast produces valid handle.
+   * If not, the returned handle is left uninitialized.
+   *
+   * @param[in] handle to An object
+   * @return handle to a ParticleEmitter object or an uninitialized handle
+   */
+  static ParticleEmitter DownCast(BaseHandle handle);
+
+  /**
+   * @brief Enables parallel processing on the CPU
+   *
+   * This flag gives a hint to attached ParticleSystem objects to use
+   * multiple threads if possible.
+   *
+   * Setting this hint may have no effect after the particle system started
+   * simulation. It should be set before calling Start() function.
+   *
+   * @param[in] enabled if True it will try to use MT
+   */
+  void EnableParallelProcessing(bool enabled);
+
+  /**
+   * @brief Tests whether parallel processing is enabled
+   *
+   * @return True if parallel processing mode is enabled.
+   */
+  [[nodiscard]] bool IsParallelProcessingEnabled() const;
+
+  /**
+   * @brief Sets emitter source
+   *
+   * ParticleSource represents objects responsible for emitting
+   * new particles as well as reusing expired particles.
+   *
+   * @param[in] particleSource valid particle emitter source object
+   *
+   */
+  void SetSource(const ParticleSource& particleSource);
+
+  /**
+   * @brief Returns currently used particle emitter source
+   *
+   * @return Handle to the ParticleSource object
+   */
+  [[nodiscard]] ParticleSource GetSource() const;
+
+  /**
+   * @brief Sets emitter domain
+   *
+   * ParticleDomain encloses the area of particle emission.
+   *
+   * @param[in] particleDomain valid particle emitter domain
+   */
+  void SetDomain(const ParticleDomain& particleDomain);
+
+  /**
+   * @brief Sets emitter domain
+   *
+   * ParticleRenderer provides implementation for rendering set of particles
+   *
+   * @param[in] particleRenderer a valid instance of ParticleRenderer
+   */
+  void SetRenderer(const ParticleRenderer& particleRenderer);
+
+  /**
+   * @brief Sets maximum particle count in the system
+   *
+   * This value is mutable but changing number of particles will
+   * force regenerating the whole system data!
+   *
+   * @param[in] maxParticleCount maximum number of particles in the system
+   */
+  void SetParticleCount(uint32_t maxParticleCount);
+
+  /**
+   * @brief Returns currently used particle emitter domain
+   *
+   * @return Handle to the ParticleDomain object
+   */
+  [[nodiscard]] ParticleDomain GetDomain() const;
+
+  /**
+   * @brief Returns currently used particle emitter renderer
+   *
+   * @return Handle to the ParticleRenderer object
+   */
+  [[nodiscard]] ParticleRenderer GetRenderer() const;
+
+
+  /**
+   * @brief Attaches particle system to an actor
+   *
+   * @param[in] actor actor to attach the particle system
+   */
+  void AttachTo(Actor actor);
+
+  /**
+   * @brief Adds new modifier
+   * @param[in] particleMmodifier valid ParticleModifier object
+   *
+   * @return Index into the modifier stack associated with added modifier
+   */
+  uint32_t AddModifier(const ParticleModifier& particleMmodifier);
+
+  /**
+   * @brief Sets particle emission rate per second
+   *
+   * @param[in] ratePerSecond maximum number of particles emitted per second
+   */
+  void SetEmissionRate(uint32_t ratePerSecond);
+
+  /**
+   * @brief Returns emission rate per second
+   *
+   * @return Value of emission rate
+   */
+  [[nodiscard]] uint32_t GetEmissionRate() const;
+
+  /**
+   * @brief number of particles to be emitted on start of the emitter
+   *
+   * @param[in] count Initial count of particles in the system
+   */
+  void SetInitialParticleCount(uint32_t count);
+
+  /**
+   * @brief returns number of particles being emitted on start of the emitter
+   *
+   * @return number of particles
+   */
+  [[nodiscard]] uint32_t GetInitialParticleCount() const;
+
+  /**
+   * @brief Sets maximum number of particles alive
+   *
+   * This function limits number of active particles in the system.
+   *
+   * If set to 0, there is no limit.
+   *
+   * If set to non-zero, if the system reaches limit, no new particles
+   * will spawn until some of them die.
+   *
+   * @param[in] count Limit of active particles
+   */
+  void SetActiveParticlesLimit(uint32_t count);
+
+  /**
+   * @brief Returns active particles limit
+   *
+   * @return Number of active particles system is limited to
+   */
+  [[nodiscard]] uint32_t GetActiveParticlesLimit() const;
+
+  /**
+   * @brief Returns modifier in stack
+   *
+   * @param[in] index Index of modifier
+   * @return Returns valid pointer to the modifier or nullptr
+   */
+  ParticleModifier GetModifierAt(uint32_t index);
+
+  /**
+   * @brief Removes modifier at specified index
+   *
+   * @param[in] index of modifier to be removed
+   */
+  void RemoveModifierAt(uint32_t index);
+
+  /**
+   * @brief Returns ParticleList object
+   *
+   * @return Valid reference to the ParticleList
+   */
+  ParticleList& GetParticleList();
+
+  /**
+   * @brief Starts emitting particles
+   */
+  void Start();
+
+  /**
+   * @brief Stops emitting particles
+   */
+  void Stop();
+
+  /**
+   * @brief Returns current emitter status
+   *
+   * @return Current emitter status
+   */
+  [[nodiscard]] Status GetStatus() const;
+
+  /**
+   * @brief Constructor
+   */
+  ParticleEmitter() = default;
+
+private:
+  /// @cond internal
+  /**
+   * @brief This constructor is used by ParticleEmitter::New() methods.
+   *
+   * @param [in] impl A pointer to a newly allocated implementation
+   */
+  ParticleEmitter(Dali::Toolkit::ParticleSystem::Internal::ParticleEmitter* impl);
+  /// @endcond
+};
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_EMITTER_H
diff --git a/dali-toolkit/public-api/particle-system/particle-list.cpp b/dali-toolkit/public-api/particle-system/particle-list.cpp
new file mode 100644 (file)
index 0000000..6b1e18a
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+#include <dali-toolkit/public-api/particle-system/particle-list.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector3>::GetType()
+{
+  return ParticleStream::StreamDataType::FLOAT3;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector4>::GetType()
+{
+  return ParticleStream::StreamDataType::FLOAT4;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector2>::GetType()
+{
+  return ParticleStream::StreamDataType::FLOAT2;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<float>::GetType()
+{
+  return ParticleStream::StreamDataType::FLOAT;
+}
+
+ParticleList ParticleList::New(uint32_t capacity, const ParticleStreamTypeFlags& defaultStreams)
+{
+  return {new ParticleSystem::Internal::ParticleList(capacity, defaultStreams)};
+}
+
+ParticleList ParticleList::DownCast(BaseHandle handle)
+{
+  return {dynamic_cast<Internal::ParticleList*>(handle.GetObjectPtr())};
+}
+
+uint32_t ParticleList::AddStream(void* defaults, size_t dataTypeSize, ParticleStream::StreamDataType dataType, bool localStream)
+{
+  return GetImplementation(*this).AddStream(dataTypeSize, defaults, dataType, "", localStream);
+}
+
+void* ParticleList::GetRawStream(uint32_t streamIndex)
+{
+  return GetImplementation(*this).GetRawStream(streamIndex);
+}
+
+void* ParticleList::GetDefaultStream(ParticleStreamTypeFlagBit streamBit)
+{
+  return GetImplementation(*this).GetDefaultStream(streamBit);
+}
+
+uint32_t ParticleList::GetActiveParticleCount()
+{
+  return GetImplementation(*this).GetActiveParticleCount();
+}
+
+ParticleList::ParticleList(Dali::Toolkit::ParticleSystem::Internal::ParticleList* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+uint32_t ParticleList::GetCapacity() const
+{
+  return GetImplementation(*this).GetParticleCount();
+}
+
+Particle ParticleList::NewParticle(float lifetime)
+{
+  return GetImplementation(*this).NewParticle(lifetime);
+}
+
+uint32_t ParticleList::GetParticleDataSize(bool includeLocalStreams)
+{
+  return GetImplementation(*this).GetStreamElementSize(includeLocalStreams);
+}
+
+std::list<Particle>& ParticleList::GetActiveParticles()
+{
+  return GetImplementation(*this).GetParticles();
+}
+
+ParticleList::ParticleList() = default;
+
+
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
diff --git a/dali-toolkit/public-api/particle-system/particle-list.h b/dali-toolkit/public-api/particle-system/particle-list.h
new file mode 100644 (file)
index 0000000..c165159
--- /dev/null
@@ -0,0 +1,274 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_LIST_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_LIST_H
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+#include <dali/public-api/common/list-wrapper.h>
+#include <cinttypes>
+
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleList;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+/**
+ * The function is a wrapper needed to retrieve data type enum through template
+ */
+template<class T>
+struct StreamDataTypeWrapper
+{
+  static ParticleStream::StreamDataType GetType()
+  {
+    return {};
+  }
+};
+
+/**
+ * @class ParticleList
+ *
+ * ParticleList defines a storage or a partial view on an existing storage of particle-related data.
+ *
+ * ParticleList contains streams of data (properties) laid out independently (non-interleaved).
+ *
+ * The layout is more optimal for:
+ * - parallel processing
+ * - data-oriented processing (CPU-cache friendly)
+ * - adding custom streams of data (for example, physics properties)
+ *
+ * Some streams are being added automatically by the emitter when certain modifiers are being added.
+ * If the modifier requires particular data it the emitter will update the list with proper stream.
+ *
+ * There are several built-in streams defined:
+ * - POSITION
+ * - VELOCITY
+ * - COLOR
+ * - OPACITY
+ * - SIZE
+ *
+ * The New() function allows adding streams upon creation.
+ *
+ * Custom streams may be used by custom renderers, modifiers and sources but also may play
+ * a role of temporary storage space when it comes to computing particle parameters.
+ *
+ * Represents all or subset of group of particles
+ */
+class DALI_TOOLKIT_API ParticleList : public Dali::BaseHandle
+{
+public:
+  /**
+   * @brief Constructor
+   */
+  ParticleList();
+
+  /**
+   * @brief Bit flags grouping built-in particle streams by type
+   */
+  struct ParticleStreamTypeFlags
+  {
+    ParticleStreamTypeFlags(const ParticleStreamTypeFlags& flags)
+    {
+      value = flags.value;
+    }
+
+    ParticleStreamTypeFlags(const ParticleStreamTypeFlagBit& bit)
+    : value(bit)
+    {
+    }
+
+    ParticleStreamTypeFlags& operator|=(ParticleStreamTypeFlagBit flagBit)
+    {
+      value |= flagBit;
+      return *this;
+    }
+
+    ParticleStreamTypeFlags operator&(ParticleStreamTypeFlagBit flagBit)
+    {
+      return (value & flagBit);
+    }
+
+    explicit operator uint32_t() const
+    {
+      return value;
+    }
+
+    explicit operator bool() const
+    {
+      return value;
+    }
+
+    uint32_t value;
+  };
+
+  /**
+   * @brief Creates new ParticleList
+   *
+   * ParticleList is a storage object that contains data for particle system
+   * layouted as an array of streams.
+   *
+   * Stream may contain data like position, velocity, etc.
+   *
+   * When new ParticleList is created the default streams can be pre-allocated.
+   *
+   * @param[in] capacity Maximum capacity (number of particles in the system)
+   * @param[in] defaultStreams Default data streams to pre-allocate
+   */
+  static ParticleList New(uint32_t capacity, const ParticleStreamTypeFlags& defaultStreams);
+
+  /**
+   * @brief Downcasts a handle to ParticleList handle.
+   *
+   * If handle points to an ParticleList object, the downcast produces valid handle.
+   * If not, the returned handle is left uninitialized.
+   *
+   * @param[in] handle to An object
+   * @return handle to a ParticleList object or an uninitialized handle
+   */
+  static ParticleList DownCast(BaseHandle handle);
+
+  /**
+   * @brief Registers new data-stream with given unique identifier
+   *
+   * @tparam T Type of data stored in the stream
+   * @param[in] defaults Default value of the stream uninitialized data
+   *
+   * Streams added using AddStream() function are automatically passed into
+   * the shader program as input attributes.
+   *
+   * If there is no need for the stream to be used as a shader attribute
+   * then use AddLocalStream() instead.
+   *
+   * @return Returns index of an allocated data stream
+   */
+  template<class T>
+  inline uint32_t AddStream(T defaults)
+  {
+    return AddStream(&defaults, StreamDataTypeWrapper<T>::GetType(), sizeof(T), false);
+  }
+
+  /**
+   * @brief Adds local data stream
+   *
+   * @tparam T Type of data stored in the stream
+   * @param[in] defaults Default value of the stream
+   *
+   * @return Returns index of newly allocated data stream
+   */
+  template<class T>
+  inline uint32_t AddLocalStream(T defaults)
+  {
+    return AddStream(&defaults, sizeof(T), StreamDataTypeWrapper<T>::GetType(), true);
+  }
+
+  template<class T>
+  T* GetStream(uint32_t streamIndex)
+  {
+    return reinterpret_cast<T*>(GetRawStream(streamIndex));
+  }
+
+  template<class T>
+  T* GetDefaultStream(ParticleStreamTypeFlagBit streamFlagBit)
+  {
+    return reinterpret_cast<T*>(GetDefaultStream(streamFlagBit));
+  }
+
+  /**
+   * @brief Returns size of list including only active particles
+   *
+   * @return
+   */
+  uint32_t GetActiveParticleCount();
+
+  /**
+   * @brief Returns capacity of particle list
+   */
+  [[nodiscard]] uint32_t GetCapacity() const;
+
+  /**
+   * Creates new particle in the list with specified lifetime
+   *
+   * @param[in] lifetime Expected lifetime of new particle (0.0f - lives forever)
+   * @return index into data streams
+   */
+  Particle NewParticle(float lifetime);
+
+  /**
+   * @brief Returns internal data size of streams
+   *
+   * @param[in] includeLocalStreams If true, the size will include local streams
+   * @return Size of data structure
+   */
+  uint32_t GetParticleDataSize(bool includeLocalStreams);
+
+  std::list<Particle>& GetActiveParticles();
+
+private:
+  /// @cond internal
+  /**
+   * @brief Adds new data stream to the list
+   *
+   * @param[in] defaults Default values to fill the stream with
+   * @param[in] dataTypeSize size of stored data type
+   * @param[in] dataType Stream data type
+   * @param[in] localStream Flag indicating whether stream is local (not used in shaders) or not
+   * @return Index of new stream
+   */
+  uint32_t
+  AddStream(void* defaults, size_t dataTypeSize, ParticleStream::StreamDataType dataType, bool localStream);
+  /// @endcond
+
+  /// @cond internal
+  /**
+   * @brief Returns raw data pointer to the stream at given index
+   *
+   * @param[in] streamIndex Stream index
+   *
+   * @return Valid data pointer or nullptr if index invalid
+   */
+  void* GetRawStream(uint32_t streamIndex);
+  /// @endcond
+
+  /// @cond internal
+  /**
+   * @brief Returns raw data stream of a default (built-in) stream
+   * @param[in] streamBit Bit (ParticleStreamTypeFlagBit) representing the stream
+   *
+   * @return Returns valid pointer to the data or nullptr if stream hasn't been used
+   */
+  void* GetDefaultStream(ParticleStreamTypeFlagBit streamBit);
+  /// @endcond
+
+  /// @cond internal
+  /**
+   * @brief This constructor is used by ParticleList::New() methods.
+   *
+   * @param [in] impl A pointer to a newly allocated implementation
+   */
+  ParticleList(Dali::Toolkit::ParticleSystem::Internal::ParticleList* impl);
+  /// @endcond
+};
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif
diff --git a/dali-toolkit/public-api/particle-system/particle-modifier.cpp b/dali-toolkit/public-api/particle-system/particle-modifier.cpp
new file mode 100644 (file)
index 0000000..472499e
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *
+ */
+
+#include <memory>
+
+#include <dali-toolkit/internal/particle-system/particle-modifier-impl.h>
+#include <dali-toolkit/public-api/particle-system/particle-modifier.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+ParticleModifier ParticleModifier::New(std::unique_ptr<ParticleModifierInterface>&& modifierUpdater)
+{
+  return {new Internal::ParticleModifier(std::move(modifierUpdater))};
+}
+
+ParticleModifier::ParticleModifier(Internal::ParticleModifier* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+ParticleModifier ParticleModifier::DownCast(BaseHandle handle)
+{
+  return {dynamic_cast<Internal::ParticleModifier*>(handle.GetObjectPtr())};
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
diff --git a/dali-toolkit/public-api/particle-system/particle-modifier.h b/dali-toolkit/public-api/particle-system/particle-modifier.h
new file mode 100644 (file)
index 0000000..de8d4e6
--- /dev/null
@@ -0,0 +1,140 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_MODIFIER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_MODIFIER_H
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+#include <dali/public-api/signals/callback.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleModifier;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+class ParticleEmitter;
+
+class ParticleList;
+
+class DALI_TOOLKIT_API ParticleModifierInterface
+{
+public:
+  /**
+   * @brief Destructor
+   */
+  virtual ~ParticleModifierInterface() = default;
+
+  /**
+   * @brief Update function to update the modifier to alter the behavior of particles.
+   * @param[in] particleList       List of particles
+   * @param[in] firstParticleIndex Index of the first particle
+   * @param[in] particleCount      Number of particles
+   */
+  virtual void Update(ParticleList& particleList, uint32_t firstParticleIndex, uint32_t particleCount) = 0;
+
+  /**
+   * @brief Will be called to check whether modifier supports multi-threading
+   *
+   * If modifier supports multi-threading then Update() function will be called
+   * providing partial range of particles to process.
+   *
+   * It is important to make sure, the batch of particles has no dependencies
+   * on particles from outside the batch. If this is the case, the function
+   * must return false and single threaded process will proceed.
+   *
+   * @return True if modifier supports multi-threading
+   */
+  virtual bool IsMultiThreaded()
+  {
+    return false;
+  }
+};
+
+/**
+ * @class ParticleModifier
+ *
+ * @brief ParticleModifer allows altering particle behaviour during simulation.
+ *
+ * Multiple modifiers can be used in the modifier stack.
+ *
+ * Output of previous modifier becomes an input of the next one.
+ *
+ */
+class DALI_TOOLKIT_API ParticleModifier : public Dali::BaseHandle
+{
+public:
+  /**
+   * @brief Constructor
+   */
+  ParticleModifier() = default;
+
+  /**
+   * @brief Destructor
+   */
+  ~ParticleModifier() = default;
+
+  /**
+   * @brief Creates new modifier with given functor object.
+   * The modifier takes over the ownership over the functor object.
+   *
+   * @param[in] modifierUpdater Functor for the modifier
+   * @return New ParticleModifier object
+   */
+  static ParticleModifier New(std::unique_ptr<ParticleModifierInterface>&& modifierUpdater);
+
+  /**
+   * @brief Creates new modifier with given class and construction arguments
+   *
+   * @return New ParticleModifier object
+   */
+  template<class T, class... Args>
+  static ParticleModifier New(Args... args)
+  {
+    return New(std::move(std::make_unique<T>(args...)));
+  }
+
+  /**
+   * @brief Downcasts a handle to ParticleModifier handle.
+   *
+   * If handle points to an ParticleModifier object, the downcast produces valid handle.
+   * If not, the returned handle is left uninitialized.
+   *
+   * @param[in] handle to An object
+   * @return handle to a ParticleModifier object or an uninitialized handle
+   */
+  static ParticleModifier DownCast(BaseHandle handle);
+
+private:
+  /// @cond internal
+  /**
+   * @brief This constructor is used by ParticleModifier::New() methods.
+   *
+   * @param [in] impl A pointer to a newly allocated implementation
+   */
+  ParticleModifier(Internal::ParticleModifier* impl);
+  /// @endcond
+};
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif
diff --git a/dali-toolkit/public-api/particle-system/particle-renderer.cpp b/dali-toolkit/public-api/particle-system/particle-renderer.cpp
new file mode 100644 (file)
index 0000000..05e9b40
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-renderer-impl.h>
+#include <dali-toolkit/public-api/particle-system/particle-renderer.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+ParticleRenderer ParticleRenderer::New()
+{
+  return {new Internal::ParticleRenderer()};
+}
+
+ParticleRenderer::ParticleRenderer(Internal::ParticleRenderer* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+void ParticleRenderer::SetTexture(const Dali::Texture& texture)
+{
+  GetImplementation(*this).SetTexture(texture);
+}
+
+void ParticleRenderer::SetBlendingMode(BlendingMode blendingMode)
+{
+  GetImplementation(*this).SetBlendingMode(blendingMode);
+}
+
+ParticleRenderer ParticleRenderer::DownCast(BaseHandle handle)
+{
+  return {dynamic_cast<Internal::ParticleRenderer*>(handle.GetObjectPtr())};
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
diff --git a/dali-toolkit/public-api/particle-system/particle-renderer.h b/dali-toolkit/public-api/particle-system/particle-renderer.h
new file mode 100644 (file)
index 0000000..0e0a35b
--- /dev/null
@@ -0,0 +1,98 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_RENDERER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_RENDERER_H
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+#include <dali/public-api/signals/callback.h>
+#include <memory>
+
+namespace Dali
+{
+class Texture;
+}
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleRenderer;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+/**
+ * @brief Blending modes for particle renderer
+ */
+enum class DALI_TOOLKIT_API BlendingMode
+{
+  ADDITIVE, ///< Additive blending mode (default)
+  DEFAULT = ADDITIVE,
+  SCREEN ///< SCREEN mode, Advanced blending support required
+};
+
+class DALI_TOOLKIT_API ParticleRenderer : public Dali::BaseHandle
+{
+public:
+  ParticleRenderer() = default;
+
+  /**
+   * @brief Creates new instance of basic 2D renderer
+   *
+   * @return Valid instance of renderer
+   */
+  static ParticleRenderer New();
+
+  /**
+   * @brief Sets blending mode for the renderer
+   *
+   * @param[in] blendingMode Valid particle blending mode to be used
+   */
+  void SetBlendingMode(BlendingMode blendingMode);
+
+  /**
+   * @brief Sets renderable as a 2D texture (sprites)
+   *
+   * @param[in] texture Valid texture
+   */
+  void SetTexture(const Dali::Texture& texture);
+
+  /**
+   * @brief Downcasts a handle to ParticleRenderer handle.
+   *
+   * If handle points to an ParticleRenderer object, the downcast produces valid handle.
+   * If not, the returned handle is left uninitialized.
+   *
+   * @param[in] handle to An object
+   * @return handle to a ParticleRenderer object or an uninitialized handle
+   */
+  static ParticleRenderer DownCast(BaseHandle handle);
+
+private:
+  /// @cond internal
+  /**
+   * @brief This constructor is used by ParticleRenderer::New() methods.
+   *
+   * @param [in] impl A pointer to a newly allocated implementation
+   */
+  ParticleRenderer(Internal::ParticleRenderer* impl);
+  /// @endcond
+};
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_RENDERER_H
\ No newline at end of file
diff --git a/dali-toolkit/public-api/particle-system/particle-source.cpp b/dali-toolkit/public-api/particle-system/particle-source.cpp
new file mode 100644 (file)
index 0000000..3051b1d
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-source-impl.h>
+#include <dali-toolkit/public-api/particle-system/particle-source.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+
+ParticleSource ParticleSource::New(std::unique_ptr<ParticleSourceInterface>&& sourceUpdater)
+{
+  return {new Internal::ParticleSource(std::move(sourceUpdater))};
+}
+
+ParticleSource::ParticleSource(Internal::ParticleSource* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+ParticleSource ParticleSource::DownCast(BaseHandle handle)
+{
+  return {dynamic_cast<Internal::ParticleSource*>(handle.GetObjectPtr())};
+}
+
+ParticleSourceInterface& ParticleSource::GetSourceCallback() const
+{
+  return GetImplementation(*this).GetUpdater();
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
diff --git a/dali-toolkit/public-api/particle-system/particle-source.h b/dali-toolkit/public-api/particle-system/particle-source.h
new file mode 100644 (file)
index 0000000..d2578a0
--- /dev/null
@@ -0,0 +1,149 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_SOURCE_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_SOURCE_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/public-api/object/base-handle.h>
+#include <dali/public-api/signals/callback.h>
+#include <memory>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleSource;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+class ParticleEmitter;
+
+class ParticleList;
+
+class ParticleDomain;
+
+class ParticleModifier;
+
+class ParticleRenderer;
+
+/**
+ * @brief Interface to override as a particle source
+ *
+ * ParticleSourceInterace allows altering behaviour for
+ * generating new particles.
+ */
+class DALI_TOOLKIT_API ParticleSourceInterface
+{
+public:
+  /**
+   * @brief Adds new particles
+   * @param[in] outList List to write back
+   * @param[in] count Requested particles count
+   *
+   * @return Function should return a Number of emitted particles
+   */
+  virtual uint32_t Update(ParticleList& outList, uint32_t count) = 0;
+
+  /**
+   * @brief Called when source is added to the emitter
+   */
+  virtual void Init() = 0;
+};
+
+/**
+ * @class ParticleSource
+ *
+ * ParticleSource defines a logic associated with particles emission.
+ * The emitter will use ParticleSource in order to spawn new particles.
+ *
+ * ParticleSource manages how, where and how many particles to emit.
+ *
+ * ParticleSource uses an implementation of ParticleSourceInterface when
+ * emitting new particles.
+ */
+class DALI_TOOLKIT_API ParticleSource : public Dali::BaseHandle
+{
+public:
+  /**
+   * @brief Constructor
+   */
+  ParticleSource() = default;
+
+  /**
+   * @brief Destructor
+   */
+  ~ParticleSource() = default;
+
+  /**
+   * @brief Creates a new instance of ParticleSource
+   *
+   * New instance is given an ownership over valid implementation
+   * of a ParticleSourceInferface.
+   *
+   * @param[in] particleSourceCallback Valid implementation of ParticleSourceInterface
+   *
+   * @return New ParticleSource object handle
+   */
+  static ParticleSource New(std::unique_ptr<ParticleSourceInterface>&& particleSourceCallback);
+
+  /**
+   * @brief Creates new ParticleSource with given source callback and arguments
+   *
+   * The function creates a ParticleSource of a given class (T) and constructor
+   * arguments.
+   *
+   * @return New ParticleSource object handle
+   */
+  template<class T, class... Args>
+  static ParticleSource New(Args... args)
+  {
+    return New(std::move(std::make_unique<T>(args...)));
+  }
+
+  /**
+   * @brief Downcasts a handle to ParticleSource handle.
+   *
+   * If handle points to an ParticleSource object, the downcast produces valid handle.
+   * If not, the returned handle is left uninitialized.
+   *
+   * @param[in] handle to An object
+   * @return handle to a ParticleSource object or an uninitialized handle
+   */
+  static ParticleSource DownCast(BaseHandle handle);
+
+  /**
+   * @brief Returns associated particle source callback
+   *
+   * @return Valid reference to associated callback
+   */
+  [[nodiscard]] ParticleSourceInterface& GetSourceCallback() const;
+
+private:
+  /// @cond internal
+  /**
+   * @brief This constructor is used by ParticleSource::New() methods.
+   *
+   * @param [in] impl A pointer to a newly allocated implementation
+   */
+  ParticleSource(Internal::ParticleSource* impl);
+  /// @endcond
+};
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_SOURCE_H
\ No newline at end of file
diff --git a/dali-toolkit/public-api/particle-system/particle-types.h b/dali-toolkit/public-api/particle-system/particle-types.h
new file mode 100644 (file)
index 0000000..383865d
--- /dev/null
@@ -0,0 +1,60 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_TYPES_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_TYPES_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 <cinttypes>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/dali-toolkit-common.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+using ParticleStreamTypeFlagBit = uint32_t;
+
+namespace ParticleStream DALI_TOOLKIT_API
+{
+constexpr ParticleStreamTypeFlagBit POSITION_STREAM_BIT = 1 << 0; ///< 3D Position stream
+constexpr ParticleStreamTypeFlagBit ROTATION_STREAM_BIT = 1 << 1; ///< 3D Rotation stream
+constexpr ParticleStreamTypeFlagBit SCALE_STREAM_BIT    = 1 << 2; ///< 3D Scale stream
+constexpr ParticleStreamTypeFlagBit SIZE_STREAM_BIT     = 1 << 3;
+constexpr ParticleStreamTypeFlagBit COLOR_STREAM_BIT    = 1 << 4;
+constexpr ParticleStreamTypeFlagBit OPACITY_STREAM_BIT  = 1 << 5;
+constexpr ParticleStreamTypeFlagBit VELOCITY_STREAM_BIT = 1 << 6;
+constexpr ParticleStreamTypeFlagBit LIFETIME_STREAM_BIT = 1 << 7;
+constexpr ParticleStreamTypeFlagBit ALL_STREAMS         = 255;
+constexpr ParticleStreamTypeFlagBit DEFAULT_STREAMS     = 255; ///< Default streams
+
+/**
+ * Stream data types
+ */
+enum class StreamDataType
+{
+  FLOAT,
+  FLOAT2,
+  FLOAT3,
+  FLOAT4,
+  INT,
+  INT2,
+  INT3,
+  INT4
+};
+
+} // namespace ParticleStream DALI_TOOLKIT_API
+} // namespace Dali::Toolkit::ParticleSystem
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_TYPES_H
\ No newline at end of file
diff --git a/dali-toolkit/public-api/particle-system/particle.cpp b/dali-toolkit/public-api/particle-system/particle.cpp
new file mode 100644 (file)
index 0000000..d94cbae
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/particle-system/particle-impl.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+
+Particle Particle::DownCast(BaseHandle handle)
+{
+  return {dynamic_cast<Internal::Particle*>(handle.GetObjectPtr())};
+}
+
+void* Particle::Get(ParticleStreamTypeFlagBit streamBit)
+{
+  return GetImplementation(*this).Get(streamBit);
+}
+
+void* Particle::GetByIndex(uint32_t streamIndex)
+{
+  return GetImplementation(*this).GetByIndex(streamIndex);
+}
+
+Particle::Particle(Dali::Toolkit::ParticleSystem::Internal::Particle* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+uint32_t Particle::GetIndex() const
+{
+  return GetImplementation(*this).GetIndex();
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
diff --git a/dali-toolkit/public-api/particle-system/particle.h b/dali-toolkit/public-api/particle-system/particle.h
new file mode 100644 (file)
index 0000000..641ef71
--- /dev/null
@@ -0,0 +1,142 @@
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_H
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+namespace Internal
+{
+class Particle;
+}
+
+/**
+ * @class Particle
+ *
+ * Particle represents a single instance of a particle in the particle system.
+ *
+ * Particle provides a simple interface that allows R/W access to selected data stream and
+ * allows ignoring a placement of the particle data within the stream (Particle serves as a
+ * view on particular data within a stream).
+ */
+class DALI_TOOLKIT_API Particle : public BaseHandle
+{
+public:
+  /**
+   * @brief Constructor
+   */
+  Particle() = default;
+
+  /**
+   * @brief Downcasts a handle to Particle handle.
+   *
+   * If handle points to an Particle object, the downcast produces valid handle.
+   * If not, the returned handle is left uninitialized.
+   *
+   * @param[in] handle to An object
+   * @return handle to a Particle object or an uninitialized handle
+   */
+  static Particle DownCast(BaseHandle handle);
+
+  /**
+   * @brief Returns writeable reference to the data for specified stream
+   *
+   * The ParticleStreamTypeFlagBit allows accessing only pre-defined streams
+   * defined by the ParticleSystem. For custom streams GetByIndex() should be used.
+   *
+   * @tparam T type of data
+   * @param[in] streamBit Stream to access data from
+   * @return Reference to the data value
+   */
+  template<class T>
+  T& Get(ParticleStreamTypeFlagBit streamBit)
+  {
+    return *reinterpret_cast<T*>(Get(streamBit));
+  }
+
+  /**
+   * @brief Returns writeable reference to the data for a stream specified by stream index.
+   *
+   * This function allows accessing builtin streams as well as custom ones. The index of a custom
+   * stream should be stored upon creation.
+   *
+   * @tparam T type of data
+   * @param[in] streamIndex Index of stream in the emitter
+   * @return Reference to the data value
+   */
+  template<class T>
+  T& GetByIndex(uint32_t streamIndex)
+  {
+    return *reinterpret_cast<T*>(GetByIndex(streamIndex));
+  }
+
+  /**
+   * @brief Returns an index of particle within emitter data streams.
+   *
+   * @return Index of particle
+   */
+  [[nodiscard]] uint32_t GetIndex() const;
+
+public:
+  /// @cond internal
+  /**
+   * @brief This constructor is used by Particle::New() methods.
+   *
+   * @param [in] impl A pointer to a newly allocated implementation
+   */
+  Particle(Dali::Toolkit::ParticleSystem::Internal::Particle* impl);
+  /// @endcond
+
+private:
+  /// @cond internal
+  /**
+   * @brief Returns pointer to the value
+   *
+   *
+   * The ParticleStreamTypeFlagBit allows accessing only pre-defined streams
+   * defined by the ParticleSystem. For custom streams GetByIndex() should be used.
+   *
+   * @param[in] streamBit Stream to access data from
+   *
+   * @return void* to the memory within stream that stores the data
+   */
+  void* Get(ParticleStreamTypeFlagBit streamBit);
+  /// @endcond
+
+  /// @cond internal
+  /**
+   * @brief Returns writeable reference to the data for a stream specified by stream index.
+   *
+   * This function allows accessing builtin streams as well as custom ones. The index of a custom
+   * stream should be stored upon creation.
+   *
+   * @param[in] streamIndex Index of stream in the emitter
+   *
+   * @return void* to the memory within stream that stores the data
+   */
+  void* GetByIndex(uint32_t streamIndex);
+  /// @endcond
+};
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_H
\ No newline at end of file
index c466716..4c7ff58 100644 (file)
@@ -1,6 +1,6 @@
 Name:       dali2-toolkit
 Summary:    Dali 3D engine Toolkit
-Version:    2.2.24
+Version:    2.2.26
 Release:    1
 Group:      System/Libraries
 License:    Apache-2.0 and BSD-3-Clause and MIT