2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali-toolkit/dali-toolkit.h>
19 #include <dali/dali.h>
22 #include <chrono> // std::chrono::system_clock
24 #include <random> // std::default_random_engine
27 #include "shared/utility.h"
28 #include "sparkle-effect.h"
31 using Dali::Toolkit::ImageView;
33 using namespace SparkleEffect;
35 namespace // unnamed namespace
37 //background image for normal status
38 const char* const CIRCLE_BACKGROUND_IMAGE(DEMO_IMAGE_DIR "sparkle_normal_background.png");
39 //particle shape image
40 const char* const PARTICLE_IMAGE(DEMO_IMAGE_DIR "sparkle_particle.png");
42 float EaseOutSquare(float progress)
44 return 1.0f - (1.0f - progress) * (1.0f - progress);
47 float CustomBounce(float progress)
49 float p = 1.f - progress;
51 return 17.68f * p * p * p * progress;
54 float Mix(const Vector2& range, float a)
56 return range.x * a + range.y * (1.f - a) - 0.001f;
59 const Vector4 BACKGROUND_COLOR(0.f, 0.f, 0.05f, 1.f);
61 } // unnamed namespace
63 // This example shows a sparkle particle effect
65 class SparkleEffectExample : public ConnectionTracker
69 * Create the SparkleEffectExample
70 * @param[in] application The DALi application instance
72 SparkleEffectExample(Application& application)
73 : mApplication(application),
77 mApplication.InitSignal().Connect(this, &SparkleEffectExample::OnInit);
82 * Initialize the SparkleEffectExample
83 * @param[in] application The DALi application instance
85 void OnInit(Application& application)
87 Window window = application.GetWindow();
88 window.KeyEventSignal().Connect(this, &SparkleEffectExample::OnKeyEvent);
89 window.SetBackgroundColor(BACKGROUND_COLOR);
91 mCircleBackground = ImageView::New(CIRCLE_BACKGROUND_IMAGE);
92 mCircleBackground.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
93 mCircleBackground.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
95 window.Add(mCircleBackground);
97 mEffect = SparkleEffect::New();
99 mMeshActor = CreateMeshActor();
101 window.Add(mMeshActor);
103 mMeshActor.SetProperty(Actor::Property::POSITION, ACTOR_POSITION);
104 mMeshActor.SetProperty(Actor::Property::SCALE, ACTOR_SCALE);
106 mTapDetector = TapGestureDetector::New();
107 mTapDetector.Attach(mCircleBackground);
108 mTapDetector.DetectedSignal().Connect(this, &SparkleEffectExample::OnTap);
110 mPanGestureDetector = PanGestureDetector::New();
111 mPanGestureDetector.DetectedSignal().Connect(this, &SparkleEffectExample::OnPan);
112 mPanGestureDetector.Attach(mCircleBackground);
114 PlayWanderAnimation(35.f);
118 * Create the mesh representing all the particles
120 Actor CreateMeshActor()
122 // shuffling to assign the color in random order
123 unsigned int* shuffleArray = new unsigned int[NUM_PARTICLE];
124 for(unsigned int i = 0; i < NUM_PARTICLE; i++)
128 const unsigned int seed = std::chrono::system_clock::now().time_since_epoch().count();
129 std::shuffle(&shuffleArray[0], &shuffleArray[NUM_PARTICLE], std::default_random_engine(seed));
133 std::vector<Vertex> vertices;
134 std::vector<unsigned short> faces;
136 for(unsigned int i = 0; i < NUM_PARTICLE; i++)
138 float colorIndex = GetColorIndex(shuffleArray[i]);
139 AddParticletoMesh(vertices, faces, PATHS[i], colorIndex);
142 delete[] shuffleArray;
144 Property::Map vertexFormat;
145 vertexFormat["aTexCoord"] = Property::VECTOR2;
146 vertexFormat["aParticlePath0"] = Property::VECTOR2;
147 vertexFormat["aParticlePath1"] = Property::VECTOR2;
148 vertexFormat["aParticlePath2"] = Property::VECTOR2;
149 vertexFormat["aParticlePath3"] = Property::VECTOR2;
150 vertexFormat["aParticlePath4"] = Property::VECTOR2;
151 vertexFormat["aParticlePath5"] = Property::VECTOR2;
153 VertexBuffer vertexBuffer = VertexBuffer::New(vertexFormat);
154 vertexBuffer.SetData(&vertices[0], vertices.size());
156 Geometry geometry = Geometry::New();
157 geometry.AddVertexBuffer(vertexBuffer);
158 geometry.SetIndexBuffer(&faces[0], faces.size());
159 geometry.SetType(Geometry::TRIANGLES);
161 Texture particleTexture = DemoHelper::LoadTexture(PARTICLE_IMAGE);
162 TextureSet textureSet = TextureSet::New();
163 textureSet.SetTexture(0u, particleTexture);
165 Renderer renderer = Renderer::New(geometry, mEffect);
166 renderer.SetTextures(textureSet);
168 Actor meshActor = Actor::New();
169 meshActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
170 meshActor.SetProperty(Actor::Property::SIZE, Vector2(1, 1));
171 meshActor.SetProperty(Actor::Property::UPDATE_AREA_HINT, ACTOR_UPDATE_AREA_HINT);
172 meshActor.AddRenderer(renderer);
178 * Defines a rule to assign particle with a color according to its index
180 float GetColorIndex(unsigned int particleIndex)
182 unsigned int thereshold = 0;
183 for(unsigned int i = 0; i < NUM_COLOR; i++)
185 thereshold += PARTICLE_COLORS[i].numParticle;
186 if(particleIndex < thereshold)
188 return i + Mix(PARTICLE_COLORS[i].AlphaRange, static_cast<float>(thereshold - particleIndex) / PARTICLE_COLORS[i].numParticle);
191 return NUM_COLOR - 1;
195 * All a particle to the mesh by giving the moving path and color index
197 * Two triangles per particle
205 * The information we need to pass in through attribute include:
207 * path which contains 12 integer
208 * ---- passed in 6 Vector2 attributes
210 * color index, particle index and textureCoor( (0,0) or (1,0) or (0,1) or (1,1) )
211 * ---- package these info into texCood attribute as: (+-colorIndex, +-particleIndex)
213 void AddParticletoMesh(std::vector<Vertex>& vertices,
214 std::vector<unsigned short>& faces,
215 MovingPath& movingPath,
218 unsigned int idx = vertices.size();
220 // store the path into position and normal, which would be decoded inside the shader
221 Vector2 particlePath0(movingPath[0], movingPath[1]);
222 Vector2 particlePath1(movingPath[2], movingPath[3]);
223 Vector2 particlePath2(movingPath[4], movingPath[5]);
224 Vector2 particlePath3(movingPath[6], movingPath[7]);
225 Vector2 particlePath4(movingPath[8], movingPath[9]);
226 Vector2 particlePath5(movingPath[10], movingPath[11]);
228 float particleIdx = static_cast<float>(idx / 4 + 1); // count from 1
229 float colorIdx = colorIndex + 1.f; // count from 1
230 vertices.push_back(Vertex{Vector2(-colorIdx, -particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5});
231 vertices.push_back(Vertex{Vector2(-colorIdx, particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5});
232 vertices.push_back(Vertex{Vector2(colorIdx, particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5});
233 vertices.push_back(Vertex{Vector2(colorIdx, -particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5});
235 faces.push_back(idx);
236 faces.push_back(idx + 1);
237 faces.push_back(idx + 2);
239 faces.push_back(idx);
240 faces.push_back(idx + 2);
241 faces.push_back(idx + 3);
245 * Main key event handler
247 void OnKeyEvent(const KeyEvent& event)
249 if(event.GetState() == KeyEvent::DOWN)
251 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
259 * Callback of the TapGesture
261 void OnTap(Actor actor, const TapGesture& tap)
264 PlayTapAnimation(5.f, tap.GetLocalPoint());
269 * Callback of the PanGesture
271 void OnPan(Actor actor, const PanGesture& gesture)
273 if(gesture.GetState() == GestureState::FINISHED)
275 switch(mAnimationIndex)
279 PlayParticleFadeAnimation(0, NUM_PARTICLE, 0.f, 3.f);
284 PlayBreakAnimation(2.0f);
289 PlayShakeAnimation(0.5f, 2.5f);
298 mAnimationIndex = (mAnimationIndex + 1) % 3;
303 * Animate the particle position to make them wandering on the screen with 'seemingly' random fade in/out
304 * @param[in] duration The duration for the particle to move a cycle on the path. the bigger this value the slower the floating movement.
305 * @param[in] looping Infinite playing or not
307 void PlayWanderAnimation(float duration, bool looping = true)
309 Animation wanderAnimation = Animation::New(duration);
310 wanderAnimation.AnimateTo(Property(mEffect, PERCENTAGE_UNIFORM_NAME), 1.f);
311 wanderAnimation.SetLooping(looping); // infinite playing
313 wanderAnimation.Play();
317 * Accelerate the particle moving speed
318 * @param[in] cycle How many extra cycles to move during the animation
319 * @param[in] duration The duration for the animation
321 void PlayShakeAnimation(float cycle, float duration)
327 DestroyAnimation(mTapAnimationAux);
329 float accelaration = GetFloatUniformValue(ACCELARATION_UNIFORM_NAME);
330 mEffect.SetProperty(mEffect.GetPropertyIndex(ACCELARATION_UNIFORM_NAME), accelaration - int(accelaration)); // Set the value as its fractional part
331 Animation shakeAnimation = Animation::New(duration);
332 shakeAnimation.AnimateBy(Property(mEffect, ACCELARATION_UNIFORM_NAME), cycle, AlphaFunction::EASE_OUT);
333 shakeAnimation.FinishedSignal().Connect(this, &SparkleEffectExample::OnShakeAnimationFinished);
335 shakeAnimation.Play();
340 * Animate the particles to appear from center and spread all over around
341 * @param[in] duration The duration for the animation
343 void PlayBreakAnimation(float duration)
345 if(GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f)
350 // Stop the fading / tap animation before the breaking
351 DestroyAnimation(mFadeAnimation);
352 mTapIndices.x = mTapIndices.y;
353 mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
354 mEffect.SetProperty(mEffect.GetPropertyIndex(ACCELARATION_UNIFORM_NAME), 0.f);
356 // prepare the animation by setting the uniform to the required value
357 mEffect.SetProperty(mEffect.GetPropertyIndex(BREAK_UNIFORM_NAME), 1.f);
358 mMeshActor.SetProperty(Actor::Property::SCALE, 0.01f);
359 mEffect.SetProperty(mEffect.GetPropertyIndex("uScale"), 0.01f);
360 mMeshActor.SetProperty(Actor::Property::POSITION, Vector3(0.f, 0.f, 1.f));
362 Animation breakAnimation = Animation::New(duration * 1.5f);
363 breakAnimation.AnimateTo(Property(mMeshActor, Actor::Property::SCALE), Vector3(ACTOR_SCALE, ACTOR_SCALE, ACTOR_SCALE), EaseOutSquare);
364 breakAnimation.AnimateTo(Property(mEffect, "uScale"), ACTOR_SCALE, EaseOutSquare);
365 breakAnimation.AnimateTo(Property(mMeshActor, Actor::Property::POSITION), ACTOR_POSITION, EaseOutSquare);
366 breakAnimation.FinishedSignal().Connect(this, &SparkleEffectExample::OnBreakAnimationFinished);
368 float timeUnit = duration / (NUM_PARTICLE + 1) / (NUM_PARTICLE + 1);
369 std::ostringstream oss;
370 for(unsigned int i = 0; i < NUM_PARTICLE; i++)
373 oss << OPACITY_UNIFORM_NAME << i << "]";
374 mEffect.SetProperty(mEffect.GetPropertyIndex(oss.str()), 0.01f);
375 float timeSlice = timeUnit * i * i;
376 breakAnimation.AnimateTo(Property(mEffect, oss.str()), 1.f, AlphaFunction::EASE_IN_OUT_SINE, TimePeriod(timeSlice * 0.5f, timeSlice));
379 breakAnimation.Play();
383 * Animate the particle opacity
384 * Particles with index between startIndex ~ startIndex+numParticle-1 fade to the target opacity one after another
385 * @param[in] startIndex The index of the first particle
386 * @param[in] numParticle The number of particle to change opacity
387 * @param[in] targetValue The final opacity
388 * @param[in] duration The duration for the animation
390 void PlayParticleFadeAnimation(unsigned int startIndex, unsigned int numParticle, float targetValue, float duration)
392 if(GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f)
397 // start the opacity animation one particle after another gradually
398 float timeSlice = duration / (numParticle + 1);
399 float fadeDuration = timeSlice > 0.5f ? timeSlice : 0.5f;
401 Animation fadeAnimation = Animation::New(duration + fadeDuration * 2.f);
402 std::ostringstream oss;
403 for(unsigned int i = startIndex; i < numParticle; i++)
405 if(i >= NUM_PARTICLE) break; // out of bound
408 oss << OPACITY_UNIFORM_NAME << i << "]";
409 fadeAnimation.AnimateTo(Property(mEffect, oss.str()), targetValue, TimePeriod(timeSlice * i, fadeDuration * 2.f));
412 fadeAnimation.Play();
413 mFadeAnimation = fadeAnimation;
414 mFadeAnimation.FinishedSignal().Connect(this, &SparkleEffectExample::OnFadeAnimationFinished);
418 * Push the particles to the edge all around the circle then bounce back
419 * @param[in] duration The duration for the animation
420 * @param[in] tapPoint The position of the tap point
422 void PlayTapAnimation(float duration, const Vector2& tapPoint)
424 if(mTapIndices.y > mTapIndices.x && mTapAnimation.GetCurrentProgress() < 0.2f)
429 Animation animation = Animation::New(duration);
430 int idx = int(mTapIndices.y) % MAXIMUM_ANIMATION_COUNT;
431 mTapIndices.y += 1.f;
433 std::ostringstream oss;
434 oss << TAP_OFFSET_UNIFORM_NAME << idx << "]";
435 mEffect.SetProperty(mEffect.GetPropertyIndex(oss.str()), 0.f);
436 animation.AnimateTo(Property(mEffect, oss.str()), 0.75f, CustomBounce);
439 oss << TAP_POINT_UNIFORM_NAME << idx << "]";
440 mEffect.SetProperty(mEffect.GetPropertyIndex(oss.str()), tapPoint / ACTOR_SCALE);
442 mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
446 mTapAnimationAux = Animation::New(duration * 0.2f);
447 mTapAnimationAux.AnimateBy(Property(mEffect, ACCELARATION_UNIFORM_NAME), 0.15f, AlphaFunction::EASE_IN_OUT);
448 mTapAnimationAux.Play();
451 mTapAnimationIndexPair[animation] = static_cast<int>(mTapIndices.y - 1.f);
452 animation.FinishedSignal().Connect(this, &SparkleEffectExample::OnTapAnimationFinished);
453 mTapAnimation = animation;
457 * Callback of the animation finished signal
459 void OnShakeAnimationFinished(Animation& animation)
465 * Callback of the animation finished signal
467 void OnFadeAnimationFinished(Animation& animation)
469 mFadeAnimation.Clear();
470 mFadeAnimation.Reset();
474 * Callback of the animation finished signal
476 void OnBreakAnimationFinished(Animation& animation)
478 mEffect.SetProperty(mEffect.GetPropertyIndex(BREAK_UNIFORM_NAME), 0.f);
482 * Callback of the animation finished signal
484 void OnTapAnimationFinished(Animation& animation)
486 if(mTapAnimationIndexPair[animation] == static_cast<int>(mTapIndices.x))
488 mTapIndices.x += 1.f;
489 if(mTapIndices.x >= mTapIndices.y)
491 mTapIndices = Vector2::ZERO;
493 mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
496 mTapAnimationIndexPair.erase(animation);
497 if(mTapAnimationIndexPair.size() < 1 && mTapIndices != Vector2::ZERO)
499 mTapIndices = Vector2::ZERO;
500 mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
508 * Helper retrieve a uniform value from the Sparkle effect shader
509 * @param[in] uniformName The uniform
510 * @return The float value
512 float GetFloatUniformValue(const std::string& uniformName)
515 mEffect.GetProperty(mEffect.GetPropertyIndex(uniformName)).Get(value);
520 * Terminate the given animation
522 void DestroyAnimation(Animation& animation)
532 Application& mApplication;
534 ImageView mCircleBackground;
537 PanGestureDetector mPanGestureDetector;
538 TapGestureDetector mTapDetector;
540 Animation mFadeAnimation;
541 Animation mTapAnimation;
542 Animation mTapAnimationAux;
545 unsigned int mAnimationIndex;
548 std::map<Animation, int> mTapAnimationIndexPair;
551 int DALI_EXPORT_API main(int argc, char** argv)
553 Application application = Application::New(&argc, &argv);
554 SparkleEffectExample theApp(application);
555 application.MainLoop();