<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>
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));
--- /dev/null
+#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
--- /dev/null
+#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_
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+#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_
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#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_
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"
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"
#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")
#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"