Added Particles example. 42/246542/9
authorGyörgy Straub <g.straub@partner.samsung.com>
Fri, 30 Oct 2020 15:53:20 +0000 (15:53 +0000)
committerAdeel Kazmi <adeel.kazmi@samsung.com>
Thu, 5 Nov 2020 17:48:47 +0000 (17:48 +0000)
- to tilt world, tilt device or if tilt isn't supported, swipe;
- tap to scatter particles;
- double click to trigger color change;

Change-Id: I8c72ea1ff7a8226b03b5e106936ca3644ef1f5b3

12 files changed:
com.samsung.dali-demo.xml
demo/dali-demo.cpp
examples/particles/float-rand.h [new file with mode: 0644]
examples/particles/particle-field.h [new file with mode: 0644]
examples/particles/particle-view.cpp [new file with mode: 0644]
examples/particles/particle-view.h [new file with mode: 0644]
examples/particles/particles-example.cpp [new file with mode: 0644]
examples/particles/utils.cpp [new file with mode: 0644]
examples/particles/utils.h [new file with mode: 0644]
resources/po/en_GB.po
resources/po/en_US.po
shared/dali-demo-strings.h

index 10425fb..174f29c 100644 (file)
        <ui-application appid="page-turn-view.example" exec="/usr/apps/com.samsung.dali-demo/bin/page-turn-view.example" nodisplay="true" multiple="false" type="c++app" taskmanage="true">
                <label>Page Turn</label>
        </ui-application>
+       <ui-application appid="particles.example" exec="/usr/apps/com.samsung.dali-demo/bin/particles.example" nodisplay="true" multiple="false" type="c++app" taskmanage="true">
+               <label>Particles</label>
+       </ui-application>
        <ui-application appid="perf-scroll.example" exec="/usr/apps/com.samsung.dali-demo/bin/perf-scroll.example" nodisplay="true" multiple="false" type="c++app" taskmanage="true">
                <label>perf-scroll</label>
        </ui-application>
index eecd9ad..677c4de 100644 (file)
@@ -50,6 +50,7 @@ int DALI_EXPORT_API main(int argc, char** argv)
   demo.AddExample(Example("metaball-refrac.example", DALI_DEMO_STR_TITLE_METABALL_REFRAC));
   demo.AddExample(Example("motion-blur.example", DALI_DEMO_STR_TITLE_MOTION_BLUR));
   demo.AddExample(Example("page-turn-view.example", DALI_DEMO_STR_TITLE_PAGE_TURN));
+  demo.AddExample(Example("particles.example", DALI_DEMO_STR_TITLE_PARTICLES));
   demo.AddExample(Example("reflection-demo.example", DALI_DEMO_STR_TITLE_REFLECTION));
   demo.AddExample(Example("refraction-effect.example", DALI_DEMO_STR_TITLE_REFRACTION));
   demo.AddExample(Example("renderer-stencil.example", DALI_DEMO_STR_TITLE_RENDERER_STENCIL));
diff --git a/examples/particles/float-rand.h b/examples/particles/float-rand.h
new file mode 100644 (file)
index 0000000..7589a8e
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef PARTICLES_FLOAT_RAND_H_
+#define PARTICLES_FLOAT_RAND_H_
+/*
+ * Copyright (c) 2020 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 <random>
+
+struct FloatRand
+{
+  std::random_device mDevice;
+  std::mt19937 mMersenneTwister;
+  std::uniform_real_distribution<float> mDistribution;
+
+  FloatRand()
+  : mMersenneTwister(mDevice()),
+    mDistribution(0., 1.)
+  {}
+
+  float operator()()
+  {
+    return mDistribution(mMersenneTwister);
+  }
+};
+
+#endif //PARTICLES_FLOAT_RAND_H_
\ No newline at end of file
diff --git a/examples/particles/particle-field.h b/examples/particles/particle-field.h
new file mode 100644 (file)
index 0000000..a629572
--- /dev/null
@@ -0,0 +1,132 @@
+#ifndef PARTICLES_PARTICLE_FIELD_H_
+#define PARTICLES_PARTICLE_FIELD_H_
+/*
+ * Copyright (c) 2020 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 "utils.h"
+#include "float-rand.h"
+#include "dali/public-api/math/vector2.h"
+#include "dali/public-api/math/vector3.h"
+#include "dali/public-api/math/vector4.h"
+#include "dali/public-api/rendering/geometry.h"
+#include <vector>
+
+struct ParticleField
+{
+  float mSize;
+  Dali::Vector3 mBoxSize;
+  Dali::Vector3 mParticlesPerAxis;
+  float mSizeVariance;
+  float mNoiseAmount;    // affects color, motion (phase), twinkle (frequency, phase, size, opacity),
+  float mDisperse;
+  float mMotionScale;
+  float mMotionCycleLength;    // seconds
+  float mTwinkleFrequency;    // per motion cycle
+  float mTwinkleSizeScale;
+  float mTwinkleOpacityWeight;
+
+  Dali::Vector3 GetParticlesPerAxisSafe() const
+  {
+    using namespace Dali;
+    return Vector3(std::max(1.f, FastFloor(mParticlesPerAxis.x)),
+      std::max(1.f, FastFloor(mParticlesPerAxis.y)),
+      std::max(1.f, FastFloor(mParticlesPerAxis.z)));
+  }
+
+  Dali::Geometry MakeGeometry() const
+  {
+    using namespace Dali;
+    FloatRand frandPath;
+    FloatRand frandSeed;
+    FloatRand frandPos;
+    FloatRand frandDisperse;
+    FloatRand frandSize;
+
+    struct Vertex
+    {
+      Vector3 aPosition;
+      float aSeed;
+      Vector4 aPath;
+      Vector2 aSubPosition;
+      float aSize;
+    };
+
+    const int numPatternVertices = 6;
+    Vector2 vertexPattern[numPatternVertices] = {
+      Vector2(-1.f, 1.f),
+      Vector2(-1.f, -1.f),
+      Vector2(1.f, 1.f),
+      Vector2(1.f, 1.f),
+      Vector2(-1.f, -1.f),
+      Vector2(1.f, -1.f),
+    };
+
+    Vector3 particlesPerAxis = GetParticlesPerAxisSafe();
+    auto numParticles = particlesPerAxis.x * particlesPerAxis.y * particlesPerAxis.z;
+
+    std::vector<Vertex> vertices;
+    vertices.reserve(numParticles * numPatternVertices);
+
+    Vector3 invBoxSize(1. / std::max(mBoxSize.x, 1.f),
+      1. / std::max(mBoxSize.y, 1.f),
+      1. / std::max(mBoxSize.z, 1.f));
+    Vector3 spacing(mBoxSize.x / particlesPerAxis.x,
+      mBoxSize.y / particlesPerAxis.y,
+      mBoxSize.z / particlesPerAxis.z);
+    auto offset = (mBoxSize - spacing) * .5;
+    int nx = particlesPerAxis.x;
+    int ny = particlesPerAxis.y;
+    int nxy = nx * ny;
+    for (size_t i = 0; i < numParticles; ++i)
+    {
+      Vertex v;
+      v.aPosition = Vector3((i % nx) * spacing.x, (i / nx) % ny * spacing.y, (i / nxy) * spacing.z) - offset;
+
+      Vector3 disperseDir(frandDisperse() - .5, frandDisperse() - .5, frandDisperse() - .5);
+      disperseDir.Normalize();
+
+      v.aPosition += disperseDir * (frandDisperse() * mDisperse);
+      v.aPosition *= invBoxSize;
+
+      v.aSeed = frandSeed() * mNoiseAmount;
+      v.aPath = Vector4(frandPath() - .5, frandPath() - .5, frandPath() - .5, frandPath() - .5) * mMotionScale;
+
+      const float size = mSize * ((1.f + (frandSize() - .5) * mSizeVariance) * .5f);
+      for (int j = 0; j < numPatternVertices; ++j)
+      {
+        v.aSubPosition = vertexPattern[j];
+        v.aSize = size;
+        vertices.push_back(v);
+      }
+    }
+
+    VertexBuffer vertexBuffer = VertexBuffer::New( Property::Map()
+      .Add( "aPosition", Property::VECTOR3 )
+      .Add( "aSeed", Property::FLOAT )
+      .Add( "aPath", Property::VECTOR4 )
+      .Add( "aSubPosition", Property::VECTOR2 )
+      .Add( "aSize", Property::FLOAT )
+    );
+    vertexBuffer.SetData( vertices.data(), vertices.size() );
+
+    Geometry geometry = Geometry::New();
+    geometry.AddVertexBuffer( vertexBuffer );
+    geometry.SetType( Geometry::TRIANGLES );
+    return geometry;
+  }
+};
+
+#endif //PARTICLES_PARTICLE_FIELD_H_
diff --git a/examples/particles/particle-view.cpp b/examples/particles/particle-view.cpp
new file mode 100644 (file)
index 0000000..4af6fdb
--- /dev/null
@@ -0,0 +1,536 @@
+/*
+ * Copyright (c) 2020 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 "particle-view.h"
+#include "utils.h"
+#include "dali/public-api/animation/constraints.h"
+
+//#define ENABLE_DEBUG_VOLUME
+
+#define USE_GLSL_VERSION(version) "#version " #version "\n"
+
+using namespace Dali;
+
+namespace
+{
+
+const uint32_t POPULATION_GRANULARITY = 128;
+
+///@brief Shader for billboarded particles, where the vertices of the particles
+/// are supplied as vec3 position (particle position) + vec2 sub-position.
+const char* const PARTICLES_VSH = USE_GLSL_VERSION(300 es)
+DALI_COMPOSE_SHADER(
+  precision lowp float;
+  uniform mat4 uModelView; // DALi
+  uniform mat4 uProjection; // DALi
+  uniform vec3 uSize; // DALi
+  uniform vec4 uColor; // DALi
+
+  uniform vec3 uSecondaryColor;
+  uniform vec2 uDepthRange; // x is zNear, y is 1.f / (zFar - zNear)
+  uniform float uTwinkleFrequency;
+  uniform float uTwinkleSizeScale;
+  uniform float uTwinkleOpacityWeight;
+  uniform float uTime;
+  uniform float uFocalLength;
+  uniform float uAperture;
+  uniform float uPopulation;
+
+  struct Scatter
+  {
+    float radiusSqr;
+    float amount;
+    vec3 ray;
+  };
+
+  const int SCATTER_VARS = 6; // Must match ParticleView::mScatterProps' size.
+  uniform Scatter uScatter[SCATTER_VARS];
+
+  const int POPULATION_GRANULARITY = 128;
+  uniform float uOrderLookUp[POPULATION_GRANULARITY];
+
+  in vec3 aPosition;
+  in float aSeed;
+  in vec4 aPath;
+  in vec2 aSubPosition;
+  in float aSize;
+
+  flat out float vDepth;
+  flat out float vFocalDistance;
+  out vec2 vUvUnit;
+  flat out float vOpacity;
+  flat out vec3 vColor; // ignore alpha
+
+  float bezier(vec3 control, float alpha)
+  {
+    return mix(mix(control.x, control.y, alpha), mix(control.y, control.z, alpha), alpha);
+  }
+
+  void main() {
+    // Get random order from the look-up table, based on particle ID.
+    int particleId = gl_VertexID / 6;
+    float order = uOrderLookUp[particleId & (POPULATION_GRANULARITY - 1)];
+
+    // Get twinkle scalar
+    float twinkle = sin(uTime * floor(uTwinkleFrequency * aSeed) + fract(aSeed * 1.17137));
+
+    // Add Motion
+    float s = sin(uTime + aSeed) * .5f + .5f;  // different phase for all
+    // NOTE: you'd think that taking the bezier() calls apart would save 4 mix() calls, since
+    // the mix()es (of xy / yz / zw / wx) are all calculated twice. It turns out that the MALI
+    // compiler is already doing this; leaving it as is for readability.
+    float bx0 = bezier(aPath.xyz, s);
+    float bx1 = bezier(aPath.zwx, s);
+    float by0 = bezier(aPath.yzw, s);
+    float by1 = bezier(aPath.wxy, s);
+    vec3 motion = vec3(mix(bx0, bx1, s), mix(by0, by1, s), 0.f);
+
+    // Model to view position
+    vec3 position3 = aPosition * uSize + motion;
+
+    vec4 position = uModelView * vec4(position3, 1.f);
+
+    // Add scatter - calculated in view space, using view ray
+    vec3 normalizedPos = position.xyz / uSize;
+    for (int i = 0; i < SCATTER_VARS; ++i)
+    {
+      vec2 scatterDist = (normalizedPos - uScatter[i].ray * dot(uScatter[i].ray, normalizedPos)).xy;
+
+      // NOTE: replacing the division with a multiplication (by inverse) oddly results in more instructions (MALI).
+      float scatter = max(0.f, uScatter[i].radiusSqr - dot(scatterDist, scatterDist)) *
+        uScatter[i].amount / aSize;
+      position.xy += scatter * normalize(scatterDist) * uSize.xy;
+    }
+
+    // Calculate normalised depth and distance from focal plane
+    float depth = (position.z - uDepthRange.x) * uDepthRange.y;
+    vDepth = depth;
+
+    float focalDist = (uFocalLength - depth) * uAperture;
+    focalDist *= focalDist;
+    vFocalDistance = max(focalDist, 1e-6f);    // NOTE: was clamp(..., 1.f); side effect: out of focus particles get squashed at higher aperture values.
+
+    // Calculate expiring scale - for size and opacity.
+    float expiringScale = smoothstep(order + 1.f, order, uPopulation);
+
+    // Calculate billboard position and size
+    vec2 subPosition = aSubPosition * aSize *
+      (1.f + twinkle * aSeed * uTwinkleSizeScale) *
+      expiringScale;
+
+    // Insist on hacking the size? Do it here...
+    float sizeHack = depth + .5f;
+    // NOTE: sizeHack *= sizeHack looked slightly better.
+    subPosition *= sizeHack;
+
+    vec3 subPositionView = vec3(subPosition, 0.);
+
+    // Add billboards to view position.
+    position += vec4(subPositionView, 0.f);
+
+    // subPosition doubles as normalized (-1..1) UV.
+    vUvUnit = aSubPosition;
+
+    // Vary opacity (actor alpha) by time as well as expiring scale.
+    vOpacity = uColor.a * expiringScale *
+      (1.0f + aSeed + twinkle * uTwinkleOpacityWeight) / (2.0f + uTwinkleOpacityWeight);
+
+    // Randomize RGB using seed.
+    vec3 mixColor = vec3(fract(aSeed), fract(aSeed * 16.f), fract(aSeed * 256.f));
+    vColor = mix(uColor.rgb, uSecondaryColor, mixColor);
+
+    gl_Position = uProjection * position;
+  });
+
+///@brief Fragment shader for particles, which simulates depth of field
+/// using a combination of procedural texturing, alpha testing and alpha
+/// blending.
+const char* const PARTICLES_FSH = USE_GLSL_VERSION(300 es)
+DALI_COMPOSE_SHADER(
+  precision lowp float;
+  uniform float uAlphaTestRefValue;
+  uniform vec2 uFadeRange; // near, far
+  in vec2 vUvUnit;
+  flat in float vDepth;
+  flat in float vFocalDistance;
+  flat in float vOpacity;
+  flat in vec3 vColor;
+  out vec4 oFragColor;
+
+  const float REF_VALUE_THRESHOLD = 1. / 64.;
+
+  void main() {
+    // Softened disc pattern from normalized UVs
+    float value = 1.f - dot(vUvUnit, vUvUnit);
+
+    // Decrease area of particles 'in-focus'.
+    float refValue = (1.f - vFocalDistance) * .5f;
+    float threshold = REF_VALUE_THRESHOLD * (1.f + vDepth);
+    float alpha = pow(value, vFocalDistance) * smoothstep(refValue - threshold, refValue + threshold, value);
+    if (alpha < uAlphaTestRefValue)
+    {
+      discard;
+    }
+
+    // Apply opacity
+    alpha *= vOpacity;
+    alpha *= alpha;
+
+    // Fade particles out as they get close to the near and far clipping planes
+    alpha *= smoothstep(.0f, uFadeRange.x, vDepth) * smoothstep(1.f, uFadeRange.y, vDepth);
+
+    oFragColor = vec4(vColor, alpha);
+  });
+
+///@brief Shader for simple textured geometry.
+const char* const SIMPLE_VSH = USE_GLSL_VERSION(300 es)
+DALI_COMPOSE_SHADER(
+  precision mediump float;
+  uniform mat4 uMvpMatrix;//by DALi
+  uniform vec3 uSize;  // by DALi
+  in vec3 aPosition;
+  void main() {
+    gl_Position = uMvpMatrix * vec4(aPosition * uSize, 1.f);
+  });
+
+///@brief Shader for an unlit, unfogged, textured mesh.
+const char* const SIMPLE_FSH = USE_GLSL_VERSION(300 es)
+DALI_COMPOSE_SHADER(
+  precision mediump float;
+  uniform vec4 uColor;
+  out vec4 oFragColor;
+
+  void main() {
+    oFragColor = uColor;
+  });
+
+
+uint32_t GetSkipValue(uint32_t count, uint32_t prime)
+{
+  uint32_t skip = 0;
+  do
+  {
+    skip = (rand() % prime) * count * count + (rand() % prime) * count + (rand() % prime);
+  }
+  while (skip % prime == 0);
+  return skip;
+}
+
+}
+
+ParticleView::ParticleView(const ParticleField& field, Dali::Actor world, Dali::CameraActor camera,
+  Dali::Geometry particleGeom)
+: mWorld(world),
+  mParticleBoxSize(field.mBoxSize)
+{
+  if (!particleGeom)
+  {
+    // create particles
+    particleGeom = field.MakeGeometry();
+  }
+
+  // create shader
+  Shader particleShader = Shader::New(PARTICLES_VSH, PARTICLES_FSH, Shader::Hint::MODIFIES_GEOMETRY);
+
+  float zNear = camera.GetNearClippingPlane();
+  float zFar = camera.GetFarClippingPlane();
+  const Vector2 depthRange(zNear, 1.f / (zFar - zNear));
+  particleShader.RegisterProperty("uDepthRange", depthRange);
+
+  particleShader.RegisterProperty("uTwinkleFrequency", field.mTwinkleFrequency);
+  particleShader.RegisterProperty("uTwinkleSizeScale", field.mTwinkleSizeScale);
+  particleShader.RegisterProperty("uTwinkleOpacityWeight", field.mTwinkleOpacityWeight);
+
+  mPropPopulation = particleShader.RegisterProperty("uPopulation", 1.f);
+  mPropFocalLength = particleShader.RegisterProperty("uFocalLength", .5f);
+  mPropAperture = particleShader.RegisterProperty("uAperture", 8.f);
+  mPropAlphaTestRefValue = particleShader.RegisterProperty("uAlphaTestRefValue", 0.f);
+  mPropFadeRange = particleShader.RegisterProperty("uFadeRange", Vector2(0.f, 1.f));
+
+  // scatter variables
+  char nameBuffer[64];
+  char* writep = nameBuffer + sprintf(nameBuffer, "uScatter[");
+  for (uint32_t i = 0; i < std::extent<decltype(mScatterProps)>::value; ++i)
+  {
+    char* writep2 = writep + sprintf(writep, "%d].", i);
+
+    sprintf(writep2, "radiusSqr");
+    mScatterProps[i].mPropRadius = particleShader.RegisterProperty(nameBuffer, 0.f);
+
+    sprintf(writep2, "amount");
+    mScatterProps[i].mPropAmount = particleShader.RegisterProperty(nameBuffer, 0.f);
+
+    sprintf(writep2, "ray");
+    mScatterProps[i].mPropRay = particleShader.RegisterProperty(nameBuffer, Vector3::ZERO);
+  }
+
+  // Create a look-up table for pseudo-random traversal of particles.
+  // Our particle mesh is sorted in Z; changing the population should remove
+  // particles "randomly", not from one end.
+  // Algorithm described in Mike McShaffry & al: Game Coding Complete.
+  const uint32_t prime = 131;  // next prime after POPULATION_GRANULARITY
+  const uint32_t skip = GetSkipValue(POPULATION_GRANULARITY, prime);
+  uint32_t next = 0;
+
+  writep = nameBuffer + sprintf(nameBuffer, "uOrderLookUp[");
+  for (uint32_t i = 0; i < POPULATION_GRANULARITY; ++i)
+  {
+    do {
+      next += skip;
+      next %= prime;
+    }
+    while (next == 0 || next > POPULATION_GRANULARITY);
+
+    sprintf(writep, "%d]", i);
+    particleShader.RegisterProperty(nameBuffer, float(next - 1));
+  }
+
+  // create animation for time in shader
+  auto propTime = particleShader.RegisterProperty("uTime", 0.f);
+
+  Animation animTime = Animation::New(field.mMotionCycleLength);
+  animTime.AnimateTo(Property(particleShader, propTime), static_cast<float>(M_PI * 2.f));
+  animTime.SetLoopCount(0);
+  animTime.Play();
+
+  mParticleShader = particleShader;
+
+  auto renderer = CreateRenderer(TextureSet::New(), particleGeom, particleShader, OPTION_BLEND);
+  auto masterParticles = CreateActor();
+  masterParticles.SetProperty(Actor::Property::SIZE, field.mBoxSize);
+  masterParticles.SetProperty(Actor::Property::VISIBLE, true);
+  masterParticles.AddRenderer(renderer);
+
+  mPropSecondaryColor = masterParticles.RegisterProperty("uSecondaryColor", Vector3::XAXIS);
+
+#ifdef ENABLE_DEBUG_VOLUME
+  Geometry cubeGeom = CreateCuboidWireframeGeometry();
+  renderer = CreateRenderer(renderer.GetTextures(), cubeGeom, Shader::New(SIMPLE_VSH, SIMPLE_FSH));
+  masterParticles.AddRenderer(renderer);
+#endif
+
+  world.Add(masterParticles);
+  mMasterParticles = masterParticles;
+}
+
+ParticleView::~ParticleView()
+{
+  UnparentAndReset(mMasterParticles);
+  UnparentAndReset(mSlaveParticles);
+
+  for (auto anim: { mAngularAnim, mLinearAnim })
+  {
+    if (anim)
+    {
+      anim.Stop();
+      anim.Reset();
+    }
+  }
+
+  for (auto& s: mScatterProps)
+  {
+    auto& anim = s.mAnim;
+    if (anim)
+    {
+      anim.Stop();
+      anim.Reset();
+    }
+  }
+}
+
+void ParticleView::SetColorRange(const ColorRange& range)
+{
+  mMasterParticles.SetProperty(Actor::Property::COLOR_RED, range.rgb0.r);
+  mMasterParticles.SetProperty(Actor::Property::COLOR_GREEN, range.rgb0.g);
+  mMasterParticles.SetProperty(Actor::Property::COLOR_BLUE, range.rgb0.b);
+
+  mMasterParticles.SetProperty(mPropSecondaryColor, range.rgb1);
+}
+
+void ParticleView::SetPopulation(float percentage)
+{
+  percentage = 1.f - std::min(1.f, std::max(0.f, percentage));
+  mParticleShader.SetProperty(mPropPopulation, POPULATION_GRANULARITY * percentage);
+}
+
+void ParticleView::SetFocalLength(float f)
+{
+  mParticleShader.SetProperty(mPropFocalLength, f);
+}
+
+void ParticleView::SetAperture(float a)
+{
+  mParticleShader.SetProperty(mPropAperture, a);
+}
+
+void ParticleView::SetAlphaTestRefValue(float rv)
+{
+  mParticleShader.SetProperty(mPropAlphaTestRefValue, rv);
+}
+
+void ParticleView::SetFadeRange(float near, float far)
+{
+  mParticleShader.SetProperty(mPropFadeRange, Vector2(near, far));
+}
+
+void ParticleView::SetAngularVelocity(float v)
+{
+  if (mAngularAnim)
+  {
+    mAngularAnim.Stop();
+    mAngularAnim.Clear();
+    mAngularAnim.Reset();
+  }
+
+  if (v * v > .0f)
+  {
+    float sign = Sign(v);
+    auto anim = Animation::New(std::abs(2. * M_PI / v));
+    anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION),
+      Quaternion(Radian(Degree(120. * sign)), Vector3::ZAXIS), TimePeriod(0., anim.GetDuration() / 3.));
+    anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION),
+      Quaternion(Radian(Degree(240. * sign)), Vector3::ZAXIS), TimePeriod(anim.GetDuration() / 3., anim.GetDuration() / 3.));
+    anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION),
+      Quaternion(Radian(Degree(360. * sign)), Vector3::ZAXIS), TimePeriod(2. * anim.GetDuration() / 3., anim.GetDuration() / 3.));
+    anim.SetLoopCount(0);
+    anim.Play();
+
+    mAngularAnim = anim;
+  }
+}
+
+void ParticleView::SetLinearVelocity(float v)
+{
+  if (mLinearAnim)
+  {
+    mLinearAnim.Stop();
+    mLinearAnim.Clear();
+    mLinearAnim.Reset();
+  }
+  UnparentAndReset(mSlaveParticles);
+
+  if (v * v > .0f)
+  {
+    float sign = Sign(v);
+    float directedSize = sign * mParticleBoxSize.z;
+
+    Actor slaveParticles = CloneActor(mMasterParticles);
+    Vector3 position = mMasterParticles.GetCurrentProperty(Actor::Property::POSITION).Get<Vector3>();
+    slaveParticles.SetProperty(Actor::Property::POSITION, position + Vector3(0., 0., directedSize));
+
+    auto propSecondaryColor = slaveParticles.RegisterProperty("uSecondaryColor", Vector3::XAXIS);
+
+    Actor world = mWorld.GetHandle();
+    world.Add(slaveParticles);
+
+    if (sign < 0.)     // fix draw order
+    {
+      world.Remove(mMasterParticles);
+      world.Add(mMasterParticles);
+    }
+
+    Constraint constraint = Constraint::New<Vector4>(slaveParticles, Actor::Property::COLOR,
+      EqualToConstraint());
+    constraint.AddSource(Source(mMasterParticles, Actor::Property::COLOR));
+    constraint.Apply();
+
+    constraint = Constraint::New<Vector3>(slaveParticles, propSecondaryColor,
+      EqualToConstraint());
+    constraint.AddSource(Source(mMasterParticles, mPropSecondaryColor));
+    constraint.Apply();
+
+    constraint = Constraint::New<Quaternion>(slaveParticles, Actor::Property::ORIENTATION,
+      EqualToConstraint());
+    constraint.AddSource(Source(mMasterParticles, Actor::Property::ORIENTATION));
+    constraint.Apply();
+
+    auto anim = Animation::New(std::abs(directedSize / v));
+    anim.AnimateTo(Property(mMasterParticles, Actor::Property::POSITION_Z), position.z - directedSize);
+    anim.AnimateTo(Property(slaveParticles, Actor::Property::POSITION_Z), position.z);
+    anim.SetLoopCount(0);
+    anim.Play();
+
+    mLinearAnim = anim;
+    mSlaveParticles = slaveParticles;
+  }
+}
+
+void ParticleView::Scatter(float radius, float amount, float durationOut, float durationIn)
+{
+  mActiveScatter = (mActiveScatter + 1) % std::extent<decltype(mScatterProps)>::value;
+
+  auto& scatter = mScatterProps[mActiveScatter];
+  if (scatter.mAnim)
+  {
+    scatter.mAnim.Stop();
+  }
+
+  radius /= mParticleBoxSize.y;
+  radius *= radius;
+  mParticleShader.SetProperty(scatter.mPropRadius, radius);
+
+  Animation anim = Animation::New(durationOut + durationIn);
+  auto scatterAmount = Property(mParticleShader, scatter.mPropAmount);
+  anim.AnimateTo(scatterAmount, amount, AlphaFunction::EASE_OUT,
+    TimePeriod(0.f, durationOut));
+  anim.AnimateTo(scatterAmount, 0.f, AlphaFunction::EASE_IN_OUT_SINE,
+    TimePeriod(durationOut, durationIn));
+  anim.Play();
+
+  scatter.mAnim = anim;
+}
+
+void ParticleView::SetScatterRay(Dali::Vector3 rayDir)
+{
+  auto& scatter = mScatterProps[mActiveScatter];
+  mParticleShader.SetProperty(scatter.mPropRay, rayDir);;
+}
+
+void ParticleView::Fade(float duration, float target, AlphaFunction alphaFn,
+  std::function<void(Dali::Animation&)> onFinished)
+{
+  if (mFadeAnim)
+  {
+    mFadeAnim.Stop();
+  }
+
+  Animation anim = Animation::New(duration);
+  anim.AnimateTo(Property(mMasterParticles, Actor::Property::COLOR_ALPHA), target, alphaFn);
+  if (mSlaveParticles)
+  {
+    anim.AnimateTo(Property(mSlaveParticles, Actor::Property::COLOR_ALPHA), target, alphaFn);
+  }
+
+  if (onFinished)
+  {
+    anim.FinishedSignal().Connect(this, onFinished);
+  }
+  anim.Play();
+
+  mFadeAnim = anim;
+}
+
+void ParticleView::Fade(float duration, float target, float from, AlphaFunction alphaFn,
+  std::function<void(Dali::Animation&)> onFinished)
+{
+  mMasterParticles.SetProperty(Actor::Property::COLOR_ALPHA, from);
+  if (mSlaveParticles)
+  {
+    mSlaveParticles.SetProperty(Actor::Property::COLOR_ALPHA, from);
+  }
+
+  Fade(duration, target, alphaFn, onFinished);
+}
diff --git a/examples/particles/particle-view.h b/examples/particles/particle-view.h
new file mode 100644 (file)
index 0000000..099f568
--- /dev/null
@@ -0,0 +1,108 @@
+#ifndef PARTICLES_PARTICLE_VIEW_H_
+#define PARTICLES_PARTICLE_VIEW_H_
+/*
+ * Copyright (c) 2020 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 "particle-field.h"
+#include "dali/public-api/actors/camera-actor.h"
+#include "dali/public-api/animation/animation.h"
+#include "dali/public-api/object/weak-handle.h"
+#include "dali/public-api/rendering/shader.h"
+#include <vector>
+#include <functional>
+
+struct ColorRange
+{
+  Dali::Vector3 rgb0;
+  Dali::Vector3 rgb1;
+};
+
+class ParticleView: public Dali::ConnectionTracker
+{
+public:
+  ParticleView(const ParticleField& field, Dali::Actor world, Dali::CameraActor camera,
+    Dali::Geometry particleGeom = Dali::Geometry());
+  ~ParticleView();
+
+  void SetColorRange(const ColorRange& range);
+
+  void SetPopulation(float percentage);
+  void SetFocalLength(float f);
+  void SetAperture(float a);
+  void SetAlphaTestRefValue(float rv);
+  void SetFadeRange(float near, float far);
+  void SetAngularVelocity(float v);
+  void SetLinearVelocity(float v);
+
+  ///@brief Starts a scatter & regroup animation, cancelling any previously played animation
+  /// of the same kind. Bigger particles, and those further away from the Z axis are affected
+  /// less.
+  ///@param radius the normalised radius, within which particles are affected.
+  ///@param amount the amount of displacement applied to particles at the peak of the animation.
+  ///@param durationOut the duration of scattering, in seconds.
+  ///@param durationIn the duration of regrouping, in seconds.
+  void Scatter(float radius, float amount, float durationOut, float durationIn);
+
+  void SetScatterRay(Dali::Vector3 rayDir);
+
+  ///@brief Starts an animation to change the opacity of the particles to @a target.
+  ///@param duration Number of seconds to complete transition in.
+  ///@param target Target opacity in the 0..1.f range.
+  void Fade(float duration, float target, Dali::AlphaFunction alphaFn = Dali::AlphaFunction::DEFAULT,
+    std::function<void(Dali::Animation&)> onFinished = nullptr);
+
+  ///@brief Starts an animation to change the opacity of the particles to @a target.
+  ///@param duration Number of seconds to complete transition in.
+  ///@param target Target opacity in the 0..1.f range.
+  ///@param from The value to set the opacity to prior to the animation.
+  void Fade(float duration, float target, float from, Dali::AlphaFunction alphaFn = Dali::AlphaFunction::DEFAULT,
+    std::function<void(Dali::Animation&)> onFinished = nullptr);
+
+private: // DATA
+  struct ScatterProps
+  {
+    Dali::Property::Index mPropRadius;
+    Dali::Property::Index mPropAmount;
+    Dali::Property::Index mPropRay;
+
+    Dali::Animation mAnim;
+  };
+
+  Dali::WeakHandle<Dali::Actor> mWorld;
+  Dali::Vector3 mParticleBoxSize;
+
+  Dali::Shader mParticleShader;
+  Dali::Property::Index mPropPopulation;
+  Dali::Property::Index mPropFocalLength;
+  Dali::Property::Index mPropAperture;
+  Dali::Property::Index mPropAlphaTestRefValue;
+  Dali::Property::Index mPropFadeRange;
+
+  ScatterProps mScatterProps[6];
+  int mActiveScatter = 0;
+
+  Dali::Actor mMasterParticles;
+  Dali::Property::Index mPropSecondaryColor;
+
+  Dali::Actor mSlaveParticles;
+
+  Dali::Animation mAngularAnim;
+  Dali::Animation mLinearAnim;
+  Dali::Animation mFadeAnim;
+};
+
+#endif //PARTICLES_PARTICLE_VIEW_H_
diff --git a/examples/particles/particles-example.cpp b/examples/particles/particles-example.cpp
new file mode 100644 (file)
index 0000000..22dd815
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2018 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 "particle-view.h"
+#include "float-rand.h"
+#include "particle-field.h"
+#include "utils.h"
+#include "dali/devel-api/adaptor-framework/tilt-sensor.h"
+#include "dali/public-api/adaptor-framework/application.h"
+#include "dali/public-api/adaptor-framework/key.h"
+#include "dali/public-api/actors/camera-actor.h"
+#include "dali/public-api/actors/layer.h"
+#include "dali/public-api/events/tap-gesture-detector.h"
+#include "dali/public-api/events/pan-gesture-detector.h"
+#include "dali/public-api/events/touch-event.h"
+#include "dali/public-api/render-tasks/render-task-list.h"
+#include "dali/public-api/render-tasks/render-task.h"
+#include "dali/public-api/object/property-index-ranges.h"
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <list>
+#include <memory>
+#include <random>
+
+using namespace Dali;
+
+namespace
+{
+
+const float PARTICLE_ALPHA = .75;
+const float ALPHA_TEST_REF_VALUE = .0625;
+
+const float NEAR_FADE = 0.04;  // normalized depth
+const float FAR_FADE = 0.8;    // normalized depth
+
+const ParticleField PARTICLE_FIELD = {
+  200.f,                        // particle size
+  Vector3(2500., 2500., 7800.), // box size - depth needs to be >= camera depth range
+  Vector3(6., 6., 12.),         // number of particles
+  .333,                         // size variance
+  1.,                           // noise amount
+  200.,                         // disperse
+  250.,                         // motion scale
+  15.,                          // motion cycle length
+  6.,                           // twinkle frequency
+  .11,                          // twinkle size scale
+  .333                          // twinkle opacity weight
+};
+
+const float WORLD_ANGULAR_VELOCITY = .08; // radians per seconds
+const float WORLD_LINEAR_VELOCITY = -500; // units along z
+
+const float SCATTER_RADIUS = 450.0f;
+const float SCATTER_AMOUNT = 180.0f;
+const float SCATTER_DURATION_OUT = .8f;
+const float SCATTER_DURATION_IN = 1.5f;
+
+const float FADE_DURATION = 1.2f;
+const float FADEOUT_SPEED_MULTIPLIER = 4.f; // speed multiplier upon fading out.
+
+const float FOCAL_LENGTH = 0.5f; // normalized depth value where particles appear sharp.
+const float APERTURE = 2.2f; // distance scale - the higher it is, the quicker the particles get blurred out moving away from the focal length.
+
+const ColorRange DEFAULT_COLOR_RANGE { Vector3(0., 48. / 255., 1.), Vector3(0., 216. / 255., 1.) };
+
+const float TILT_SCALE = 0.2;
+const float TILT_RANGE_DEGREES = 30.f;
+
+FloatRand sFloatRand;
+
+class TiltFilter
+{
+public:
+  void Reset()
+  {
+    std::fill(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f));
+  }
+
+  void Add(Dali::Vector2 tilt)
+  {
+    mTiltSamples[mIdxNextSample] = tilt;
+    mIdxNextSample = (mIdxNextSample + 1) % FILTER_SIZE;
+  }
+
+  Dali::Vector2 Filter() const
+  {
+    return std::accumulate(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f)) / FILTER_SIZE;
+  }
+
+private:
+  enum { FILTER_SIZE = 8u };
+
+  Dali::Vector2 mTiltSamples[FILTER_SIZE];
+  size_t mIdxNextSample = 0;
+};
+
+class ParticlesExample : public ConnectionTracker
+{
+public:
+  ParticlesExample( Application& app )
+  : mApp( app )
+  {
+    mApp.InitSignal().Connect(this, &ParticlesExample::OnInit);
+    mApp.TerminateSignal().Connect(this, &ParticlesExample::OnTerminate);
+  }
+
+  ~ParticlesExample() = default;
+
+private:
+  Application& mApp;
+
+  CameraActor mCamera;
+
+  Actor mWorld;
+  Vector2 mAngularPosition;
+  ColorRange mColors;
+
+  std::unique_ptr<ParticleView> mParticles;
+  std::unique_ptr<ParticleView> mExpiringParticles;
+
+  TapGestureDetector mDoubleTapGesture;
+
+  TiltSensor mTiltSensor;
+  TiltFilter mTiltFilter;
+
+  PanGestureDetector mPanGesture;
+
+  void OnInit( Application& application )
+  {
+    Window window = application.GetWindow();
+    auto rootLayer = window.GetRootLayer();
+    rootLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::Behavior::LAYER_3D);
+
+    window.KeyEventSignal().Connect( this, &ParticlesExample::OnKeyEvent );
+    window.GetRootLayer().TouchedSignal().Connect( this, &ParticlesExample::OnTouched );
+
+    auto tiltSensor = TiltSensor::Get();
+    if ( tiltSensor.Start() )
+    {
+      // Get notifications when the device is tilted
+      tiltSensor.TiltedSignal().Connect( this, &ParticlesExample::OnTilted );
+    }
+    else
+    {
+      mPanGesture = PanGestureDetector::New();
+      mPanGesture.Attach(rootLayer);
+      mPanGesture.DetectedSignal().Connect(this, &ParticlesExample::OnPan);
+    }
+
+    // Get camera
+    RenderTaskList tasks = window.GetRenderTaskList();
+    RenderTask mainPass = tasks.GetTask(0);
+    CameraActor camera = mainPass.GetCameraActor();
+    mCamera = camera;
+
+    // Create world - particles and clock are added to it; this is what we apply tilt to.
+    auto world = CreateActor();
+    world.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
+    window.Add(world);
+    mWorld = world;
+
+    // Create particles
+    TriggerColorTransition(DEFAULT_COLOR_RANGE);
+
+    // Setup double tap detector for color change
+    mDoubleTapGesture = TapGestureDetector::New(2);
+    mDoubleTapGesture.Attach(rootLayer);
+    mDoubleTapGesture.DetectedSignal().Connect(this, &ParticlesExample::OnDoubleTap);
+  }
+
+  void OnTerminate(Application& app)
+  {
+    UnparentAndReset(mWorld);
+
+    mDoubleTapGesture.Reset();
+    mPanGesture.Reset();
+    mTiltSensor.Reset();
+  }
+
+  void OnPause(Application& app)
+  {
+    mTiltSensor.Stop();
+  }
+
+  void OnResume(Application& app)
+  {
+    mTiltSensor.Start();
+  }
+
+  void OnKeyEvent(const KeyEvent& event)
+  {
+    if ( event.GetState() == KeyEvent::UP)     // single keystrokes
+    {
+      if( IsKey( event, DALI_KEY_ESCAPE ) || IsKey( event, DALI_KEY_BACK ) )
+      {
+        mApp.Quit();
+      }
+    }
+  }
+
+  bool OnTouched( Actor a, const TouchEvent& event )
+  {
+    if (event.GetPointCount() > 0)
+    {
+      auto screenPos = event.GetScreenPosition(0);
+      switch (event.GetState(0))
+      {
+      case PointState::STARTED:
+        {
+          mParticles->Scatter(SCATTER_RADIUS, SCATTER_AMOUNT, SCATTER_DURATION_OUT, SCATTER_DURATION_IN);
+
+          Vector3 ray = GetViewRay(screenPos);
+          mParticles->SetScatterRay(ray);
+        }
+        break;
+
+      default:
+        break;
+      }
+    }
+
+    return false;
+  }
+
+  void OnDoubleTap(Actor /*actor*/, const TapGesture& /*gesture*/)
+  {
+    if (!mExpiringParticles)
+    {
+      mColors.rgb0 = Vector3::ONE - mColors.rgb1;
+      mColors.rgb1 = FromHueSaturationLightness(Vector3(sFloatRand() * 360.f, sFloatRand() * .5f + .5f, sFloatRand() * .25 + .75f));
+
+      TriggerColorTransition(mColors);
+    }
+  }
+
+  void OnPan(Actor actor, const PanGesture& gesture)
+  {
+    auto tilt = gesture.GetDisplacement() / Vector2(mApp.GetWindow().GetSize()) * TILT_SCALE;
+    Quaternion q(Radian(-tilt.y), Radian(tilt.x), Radian(0.f));
+    Quaternion q0 = mWorld.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();
+    mWorld.SetProperty(Actor::Property::ORIENTATION, q * q0);
+  }
+
+  void OnTilted( const TiltSensor& sensor)
+  {
+    mTiltFilter.Add(Vector2(sensor.GetPitch(), sensor.GetRoll()));
+    Vector2 tilt = mTiltFilter.Filter() * TILT_RANGE_DEGREES;
+    Quaternion q(Radian(Degree(tilt.x)), Radian(Degree(tilt.y)), Radian(0.f));
+    mWorld.SetProperty(Actor::Property::ORIENTATION, q);
+  }
+
+  Vector3 GetViewRay(const Vector2& screenPos)
+  {
+    Vector2 screenSize = mApp.GetWindow().GetSize();
+    Vector2 normScreenPos = (screenPos / screenSize) * 2.f - Vector2::ONE;
+
+    const float fov = mCamera.GetProperty(CameraActor::Property::FIELD_OF_VIEW).Get<float>();
+    const float tanFov = std::tan(fov);
+
+    const float zNear = mCamera.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get<float>();
+    const float hProj = zNear * tanFov;
+
+    const float aspectRatio = mCamera.GetProperty(CameraActor::Property::ASPECT_RATIO).Get<float>();
+    const float wProj = hProj * aspectRatio;
+
+    // Get camera orientation for view space ray casting. Assume:
+    // - this to be world space, i.e. no parent transforms;
+    // - no scaling;
+    Quaternion cameraOrientation = mCamera.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();
+
+    Matrix worldCamera;
+    worldCamera.SetTransformComponents(Vector3::ONE, cameraOrientation, Vector3::ZERO);
+
+    float* data = worldCamera.AsFloat();
+    Vector3 xWorldCamera(data[0], data[4], data[8]);
+    xWorldCamera *= wProj * normScreenPos.x / xWorldCamera.Length();
+
+    Vector3 yWorldCamera(data[1], data[5], data[9]);
+    yWorldCamera *= hProj * normScreenPos.y / yWorldCamera.Length();
+
+    Vector3 zWorldCamera(data[2], data[6], data[10]);
+    Vector3 ray = xWorldCamera + yWorldCamera + zWorldCamera * -zNear;
+    ray.Normalize();
+
+    return ray;
+  }
+
+  void TriggerColorTransition(const ColorRange& range)
+  {
+    if (mParticles)
+    {
+      mExpiringParticles = std::move(mParticles);
+
+      // NOTE: this will break the perfect looping, but we can get away with it because we're fading out.
+      mExpiringParticles->SetLinearVelocity(WORLD_LINEAR_VELOCITY * FADEOUT_SPEED_MULTIPLIER);
+
+      auto& p = mExpiringParticles;
+      mExpiringParticles->Fade(FADE_DURATION, 0.f, AlphaFunction::EASE_IN, [&p](Animation&) {
+        p.reset();
+      });
+    }
+
+    mParticles.reset(new ParticleView(PARTICLE_FIELD, mWorld, mCamera));
+    mParticles->SetColorRange(range);
+    mParticles->SetFocalLength(FOCAL_LENGTH);
+    mParticles->SetAperture(APERTURE);
+    mParticles->SetAlphaTestRefValue(ALPHA_TEST_REF_VALUE);
+    mParticles->SetFadeRange(NEAR_FADE, FAR_FADE);
+    mParticles->SetAngularVelocity(WORLD_ANGULAR_VELOCITY);
+    mParticles->SetLinearVelocity(WORLD_LINEAR_VELOCITY);
+    mParticles->Fade(FADE_DURATION, PARTICLE_ALPHA, 0.f, AlphaFunction::EASE_OUT);
+  }
+};
+
+} // nonamespace
+
+int DALI_EXPORT_API main( int argc, char **argv )
+{
+  Application application = Application::New( &argc, &argv, DEMO_THEME_PATH );
+  ParticlesExample example( application);
+  application.MainLoop();
+  return 0;
+}
diff --git a/examples/particles/utils.cpp b/examples/particles/utils.cpp
new file mode 100644 (file)
index 0000000..7f7432e
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2020 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 "utils.h"
+
+using namespace Dali;
+
+Vector3 ToHueSaturationLightness(Vector3 rgb)
+{
+  float min = std::min(rgb.r, std::min(rgb.g, rgb.b));
+  float max = std::max(rgb.r, std::max(rgb.g, rgb.b));
+
+  Vector3 hsl(max - min, 0.f, (max + min) * .5f);
+  if (hsl.x * hsl.x > .0f)
+  {
+    hsl.y = hsl.x / max;
+    if (max == rgb.r)
+    {
+      hsl.x = (rgb.g - rgb.b) / hsl.x;
+    }
+    else if(max == rgb.g)
+    {
+      hsl.x = 2.f + (rgb.b - rgb.r) / hsl.x;
+    }
+    else
+    {
+      hsl.x = 4.f + (rgb.r - rgb.g) / hsl.x;
+    }
+    hsl.x *= 60.f;
+    if (hsl.x < 0.f)
+    {
+      hsl.x += 360.f;
+    }
+  }
+  else
+  {
+    hsl.y = 0.f;
+  }
+
+  return hsl;
+}
+
+Vector3 FromHueSaturationLightness(Vector3 hsl)
+{
+  Vector3 rgb;
+  if (hsl.y * hsl.y > 0.f)
+  {
+    if(hsl.x >= 360.f)
+    {
+      hsl.x -= 360.f;
+    }
+    hsl.x /= 60.f;
+
+    int i = FastFloor(hsl.x);
+    float ff = hsl.x - i;
+    float p = hsl.z * (1.0 - hsl.y);
+    float q = hsl.z * (1.0 - (hsl.y * ff));
+    float t = hsl.z * (1.0 - (hsl.y * (1.f - ff)));
+
+    switch (i)
+    {
+    case 0:
+      rgb.r = hsl.z;
+      rgb.g = t;
+      rgb.b = p;
+      break;
+
+    case 1:
+      rgb.r = q;
+      rgb.g = hsl.z;
+      rgb.b = p;
+      break;
+
+    case 2:
+      rgb.r = p;
+      rgb.g = hsl.z;
+      rgb.b = t;
+      break;
+
+    case 3:
+      rgb.r = p;
+      rgb.g = q;
+      rgb.b = hsl.z;
+      break;
+
+    case 4:
+      rgb.r = t;
+      rgb.g = p;
+      rgb.b = hsl.z;
+      break;
+
+    case 5:
+    default:
+      rgb.r = hsl.z;
+      rgb.g = p;
+      rgb.b = q;
+      break;
+    }
+  }
+  else
+  {
+    rgb = Vector3::ONE * hsl.z;
+  }
+
+  return rgb;
+}
+
+Geometry CreateCuboidWireframeGeometry()
+{
+//
+// 2---3
+// |-  |-
+// | 6---7
+// | | | |
+// 0-|-1 |
+//  -|  -|
+//   4---5
+//
+  Vector3 vertexData[] = {
+    Vector3(-.5, -.5, -.5),
+       Vector3( .5, -.5, -.5),
+       Vector3(-.5,  .5, -.5),
+       Vector3( .5,  .5, -.5),
+       Vector3(-.5, -.5,  .5),
+       Vector3( .5, -.5,  .5),
+       Vector3(-.5,  .5,  .5),
+       Vector3( .5,  .5,  .5),
+  };
+
+  uint16_t indices[] = {
+    0, 1, 1, 3, 3, 2, 2, 0,
+       0, 4, 1, 5, 3, 7, 2, 6,
+       4, 5, 5, 7, 7, 6, 6, 4
+  };
+  VertexBuffer vertexBuffer = VertexBuffer::New( Property::Map()
+    .Add( "aPosition", Property::VECTOR3));
+  vertexBuffer.SetData(vertexData, std::extent<decltype(vertexData)>::value );
+
+  Geometry geometry = Geometry::New();
+  geometry.AddVertexBuffer( vertexBuffer );
+  geometry.SetIndexBuffer( indices, std::extent<decltype(indices)>::value );
+  geometry.SetType(Geometry::LINES);
+  return geometry;
+}
+
+Renderer CreateRenderer(TextureSet textures, Geometry geometry, Shader shader, uint32_t options)
+{
+  Renderer renderer = Renderer::New(geometry, shader);
+  renderer.SetProperty(Renderer::Property::BLEND_MODE,
+                 (options & OPTION_BLEND) ? BlendMode::ON : BlendMode::OFF);
+  renderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE,
+                 (options & OPTION_DEPTH_TEST) ? DepthTestMode::ON : DepthTestMode::OFF);
+  renderer.SetProperty(Renderer::Property::DEPTH_WRITE_MODE,
+                 (options & OPTION_DEPTH_WRITE) ? DepthWriteMode::ON : DepthWriteMode::OFF);
+  renderer.SetProperty(Renderer::Property::FACE_CULLING_MODE, FaceCullingMode::BACK);
+
+  if (!textures)
+  {
+    textures = TextureSet::New();
+  }
+
+  renderer.SetTextures(textures);
+  return renderer;
+}
+
+void CenterActor(Actor actor)
+{
+  actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER );
+  actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
+}
+
+Actor CreateActor()
+{
+  auto actor = Actor::New();
+  CenterActor(actor);
+  return actor;
+}
+
+Renderer CloneRenderer(Renderer original)
+{
+  Geometry geom = original.GetGeometry();
+  Shader shader = original.GetShader();
+  Renderer clone = Renderer::New(geom, shader);
+
+  // Copy properties.
+  Property::IndexContainer indices;
+  original.GetPropertyIndices(indices);
+
+  for (auto& i: indices)
+  {
+    auto actualIndex = PropertyRanges::DEFAULT_RENDERER_PROPERTY_START_INDEX + i;
+    clone.SetProperty(actualIndex, original.GetProperty(actualIndex));
+  }
+
+  // Copy texture references (and create TextureSet, if there's any textures).
+  TextureSet ts = original.GetTextures();
+  clone.SetTextures(ts);
+
+  return clone;
+}
+
+Actor CloneActor(Actor original)
+{
+  using namespace Dali;
+
+  auto clone = Actor::New();
+  clone.SetProperty(Actor::Property::NAME, original.GetProperty(Actor::Property::NAME));
+
+  // Copy properties.
+  // Don't copy every single one of them.
+  // Definitely don't copy resize policy related things, which will internally enable
+  // relayout, which in turn will result in losing the ability to set Z size.
+  for (auto i : {
+    Actor::Property::PARENT_ORIGIN,
+    Actor::Property::ANCHOR_POINT,
+    Actor::Property::SIZE,
+    Actor::Property::POSITION,
+    Actor::Property::ORIENTATION,
+    Actor::Property::SCALE,
+    Actor::Property::VISIBLE,
+    Actor::Property::COLOR,
+    Actor::Property::NAME,
+  })
+  {
+    clone.SetProperty(i, original.GetProperty(i));
+  }
+
+  // Clone renderers.
+  for(unsigned int i = 0; i < original.GetRendererCount(); ++i)
+  {
+    auto rClone = CloneRenderer(original.GetRendererAt(i));
+    clone.AddRenderer(rClone);
+  }
+
+  // Recurse into children.
+  for(unsigned int i = 0; i < original.GetChildCount(); ++i)
+  {
+    Actor newChild = CloneActor(original.GetChildAt(i));
+    clone.Add(newChild);
+  }
+
+  return clone;
+}
diff --git a/examples/particles/utils.h b/examples/particles/utils.h
new file mode 100644 (file)
index 0000000..10dee0d
--- /dev/null
@@ -0,0 +1,86 @@
+#ifndef PARTICLES_UTILS_H_
+#define PARTICLES_UTILS_H_
+/*
+ * Copyright (c) 2020 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/public-api/actors/actor.h"
+#include "dali/public-api/actors/actor.h"
+#include "dali/public-api/rendering/geometry.h"
+#include "dali/public-api/rendering/renderer.h"
+#include "dali/public-api/rendering/shader.h"
+#include "dali/public-api/rendering/texture.h"
+#include "dali/public-api/math/vector3.h"
+#include <cmath>
+
+//
+// Maths
+//
+inline
+float FastFloor(float x)
+{
+  return static_cast<int>(x) - static_cast<int>(x < 0);
+}
+
+///@brief Converts RGB values (in the 0..1 range) to HSL, where hue is in degrees,
+/// in the 0..360 range, and saturation and lightness are in the 0..1 range.
+Dali::Vector3 ToHueSaturationLightness(Dali::Vector3 rgb);
+
+///@brief Converts HSL values, where hue is in degrees, in the 0..360 range, and
+/// saturation and lightness are in 0..1  to RGB (in the 0..1 range)
+Dali::Vector3 FromHueSaturationLightness(Dali::Vector3 hsl);
+
+//
+// Dali entities
+//
+///@brief Creates geometry for a unit cube wireframe (i.e. vertex positions between
+/// -.5 and .5)..
+Dali::Geometry CreateCuboidWireframeGeometry();
+
+enum RendererOptions
+{
+  OPTION_NONE = 0x0,
+  OPTION_BLEND = 0x01,
+  OPTION_DEPTH_TEST = 0x02,
+  OPTION_DEPTH_WRITE = 0x04
+};
+
+///@brief Creates a renderer with the given @a textures set, @a geometry, @a shader
+/// and @a options from above.
+///@note Back face culling is on.
+///@note If textures is not a valid handle, an empty texture set will be created.
+Dali::Renderer CreateRenderer(Dali::TextureSet textures, Dali::Geometry geometry,
+  Dali::Shader shader, uint32_t options = OPTION_NONE);
+
+///@brief Sets @a actor's anchor point and parent origin to center.
+void CenterActor(Dali::Actor actor);
+
+///@brief Creates an empty and centered actor.
+Dali::Actor CreateActor();
+
+///@brief Creates a copy of @a original, sharing the same geometry and shader and
+/// copying each properties.
+///@note Breaks if @a original has any custom properties. TODO: fix.
+Dali::Renderer CloneRenderer(Dali::Renderer original);
+
+///@brief Creates a copy of @a original, cloning each renderer, and a select set
+/// of properties: parent origin, anchor point, size, position, orientation, scale,
+/// visible, color and name.
+///@note Does not copy resize policy related properties, as setting those, even if
+/// default, will break the ability to specify a size for the actor in Z.
+Dali::Actor CloneActor(Dali::Actor original);
+
+#endif //PARTICLES_UTILS_H_
index 6a3e68e..0cc2d8f 100755 (executable)
@@ -151,6 +151,9 @@ msgstr "Negotiate Size"
 msgid "DALI_DEMO_STR_TITLE_PAGE_TURN"
 msgstr "Page Turn"
 
+msgid "DALI_DEMO_STR_TITLE_PARTICLES"
+msgstr "Particles"
+
 msgid "DALI_DEMO_STR_TITLE_PERF_SCROLL"
 msgstr "Scrolling Performance"
 
index 4e7d858..23fcbec 100755 (executable)
@@ -154,6 +154,9 @@ msgstr "Negotiate Size"
 msgid "DALI_DEMO_STR_TITLE_PAGE_TURN"
 msgstr "Page Turn"
 
+msgid "DALI_DEMO_STR_TITLE_PARTICLES"
+msgstr "Particles"
+
 msgid "DALI_DEMO_STR_TITLE_PERF_SCROLL"
 msgstr "Scrolling Performance"
 
index 3569020..5fdff42 100644 (file)
@@ -89,6 +89,7 @@ extern "C"
 #define DALI_DEMO_STR_TITLE_NATIVE_IMAGE_SOURCE dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_NATIVE_IMAGE_SOURCE")
 #define DALI_DEMO_STR_TITLE_NEGOTIATE_SIZE dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_NEGOTIATE_SIZE")
 #define DALI_DEMO_STR_TITLE_PAGE_TURN dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_PAGE_TURN")
+#define DALI_DEMO_STR_TITLE_PARTICLES dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_PARTICLES")
 #define DALI_DEMO_STR_TITLE_PBR dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_PBR")
 #define DALI_DEMO_STR_TITLE_PERF_SCROLL dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_PERF_SCROLL")
 #define DALI_DEMO_STR_TITLE_POINT_MESH dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_POINT_MESH")
@@ -191,6 +192,7 @@ extern "C"
 #define DALI_DEMO_STR_TITLE_NATIVE_IMAGE_SOURCE "Native Image Source"
 #define DALI_DEMO_STR_TITLE_NEGOTIATE_SIZE "Negotiate Size"
 #define DALI_DEMO_STR_TITLE_PAGE_TURN "Page Turn"
+#define DALI_DEMO_STR_TITLE_PARTICLES "Particles"
 #define DALI_DEMO_STR_TITLE_PBR "PBR"
 #define DALI_DEMO_STR_TITLE_PERF_SCROLL "Scrolling Performance"
 #define DALI_DEMO_STR_TITLE_POINT_MESH "Point Mesh"