2 * Copyright (c) 2021 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.AddRenderer(renderer);
177 * Defines a rule to assign particle with a color according to its index
179 float GetColorIndex(unsigned int particleIndex)
181 unsigned int thereshold = 0;
182 for(unsigned int i = 0; i < NUM_COLOR; i++)
184 thereshold += PARTICLE_COLORS[i].numParticle;
185 if(particleIndex < thereshold)
187 return i + Mix(PARTICLE_COLORS[i].AlphaRange, static_cast<float>(thereshold - particleIndex) / PARTICLE_COLORS[i].numParticle);
190 return NUM_COLOR - 1;
194 * All a particle to the mesh by giving the moving path and color index
196 * Two triangles per particle
204 * The information we need to pass in through attribute include:
206 * path which contains 12 integer
207 * ---- passed in 6 Vector2 attributes
209 * color index, particle index and textureCoor( (0,0) or (1,0) or (0,1) or (1,1) )
210 * ---- package these info into texCood attribute as: (+-colorIndex, +-particleIndex)
212 void AddParticletoMesh(std::vector<Vertex>& vertices,
213 std::vector<unsigned short>& faces,
214 MovingPath& movingPath,
217 unsigned int idx = vertices.size();
219 // store the path into position and normal, which would be decoded inside the shader
220 Vector2 particlePath0(movingPath[0], movingPath[1]);
221 Vector2 particlePath1(movingPath[2], movingPath[3]);
222 Vector2 particlePath2(movingPath[4], movingPath[5]);
223 Vector2 particlePath3(movingPath[6], movingPath[7]);
224 Vector2 particlePath4(movingPath[8], movingPath[9]);
225 Vector2 particlePath5(movingPath[10], movingPath[11]);
227 float particleIdx = static_cast<float>(idx / 4 + 1); // count from 1
228 float colorIdx = colorIndex + 1.f; // count from 1
229 vertices.push_back(Vertex{Vector2(-colorIdx, -particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5});
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});
234 faces.push_back(idx);
235 faces.push_back(idx + 1);
236 faces.push_back(idx + 2);
238 faces.push_back(idx);
239 faces.push_back(idx + 2);
240 faces.push_back(idx + 3);
244 * Main key event handler
246 void OnKeyEvent(const KeyEvent& event)
248 if(event.GetState() == KeyEvent::DOWN)
250 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
258 * Callback of the TapGesture
260 void OnTap(Actor actor, const TapGesture& tap)
263 PlayTapAnimation(5.f, tap.GetLocalPoint());
268 * Callback of the PanGesture
270 void OnPan(Actor actor, const PanGesture& gesture)
272 if(gesture.GetState() == GestureState::FINISHED)
274 switch(mAnimationIndex)
278 PlayParticleFadeAnimation(0, NUM_PARTICLE, 0.f, 3.f);
283 PlayBreakAnimation(2.0f);
288 PlayShakeAnimation(0.5f, 2.5f);
297 mAnimationIndex = (mAnimationIndex + 1) % 3;
302 * Animate the particle position to make them wandering on the screen with 'seemingly' random fade in/out
303 * @param[in] duration The duration for the particle to move a cycle on the path. the bigger this value the slower the floating movement.
304 * @param[in] looping Infinite playing or not
306 void PlayWanderAnimation(float duration, bool looping = true)
308 Animation wanderAnimation = Animation::New(duration);
309 wanderAnimation.AnimateTo(Property(mEffect, PERCENTAGE_UNIFORM_NAME), 1.f);
310 wanderAnimation.SetLooping(looping); // infinite playing
312 wanderAnimation.Play();
316 * Accelerate the particle moving speed
317 * @param[in] cycle How many extra cycles to move during the animation
318 * @param[in] duration The duration for the animation
320 void PlayShakeAnimation(float cycle, float duration)
326 DestroyAnimation(mTapAnimationAux);
328 float accelaration = GetFloatUniformValue(ACCELARATION_UNIFORM_NAME);
329 mEffect.SetProperty(mEffect.GetPropertyIndex(ACCELARATION_UNIFORM_NAME), accelaration - int(accelaration)); // Set the value as its fractional part
330 Animation shakeAnimation = Animation::New(duration);
331 shakeAnimation.AnimateBy(Property(mEffect, ACCELARATION_UNIFORM_NAME), cycle, AlphaFunction::EASE_OUT);
332 shakeAnimation.FinishedSignal().Connect(this, &SparkleEffectExample::OnShakeAnimationFinished);
334 shakeAnimation.Play();
339 * Animate the particles to appear from center and spread all over around
340 * @param[in] duration The duration for the animation
342 void PlayBreakAnimation(float duration)
344 if(GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f)
349 // Stop the fading / tap animation before the breaking
350 DestroyAnimation(mFadeAnimation);
351 mTapIndices.x = mTapIndices.y;
352 mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
353 mEffect.SetProperty(mEffect.GetPropertyIndex(ACCELARATION_UNIFORM_NAME), 0.f);
355 // prepare the animation by setting the uniform to the required value
356 mEffect.SetProperty(mEffect.GetPropertyIndex(BREAK_UNIFORM_NAME), 1.f);
357 mMeshActor.SetProperty(Actor::Property::SCALE, 0.01f);
358 mEffect.SetProperty(mEffect.GetPropertyIndex("uScale"), 0.01f);
359 mMeshActor.SetProperty(Actor::Property::POSITION, Vector3(0.f, 0.f, 1.f));
361 Animation breakAnimation = Animation::New(duration * 1.5f);
362 breakAnimation.AnimateTo(Property(mMeshActor, Actor::Property::SCALE), Vector3(ACTOR_SCALE, ACTOR_SCALE, ACTOR_SCALE), EaseOutSquare);
363 breakAnimation.AnimateTo(Property(mEffect, "uScale"), ACTOR_SCALE, EaseOutSquare);
364 breakAnimation.AnimateTo(Property(mMeshActor, Actor::Property::POSITION), ACTOR_POSITION, EaseOutSquare);
365 breakAnimation.FinishedSignal().Connect(this, &SparkleEffectExample::OnBreakAnimationFinished);
367 float timeUnit = duration / (NUM_PARTICLE + 1) / (NUM_PARTICLE + 1);
368 std::ostringstream oss;
369 for(unsigned int i = 0; i < NUM_PARTICLE; i++)
372 oss << OPACITY_UNIFORM_NAME << i << "]";
373 mEffect.SetProperty(mEffect.GetPropertyIndex(oss.str()), 0.01f);
374 float timeSlice = timeUnit * i * i;
375 breakAnimation.AnimateTo(Property(mEffect, oss.str()), 1.f, AlphaFunction::EASE_IN_OUT_SINE, TimePeriod(timeSlice * 0.5f, timeSlice));
378 breakAnimation.Play();
382 * Animate the particle opacity
383 * Particles with index between startIndex ~ startIndex+numParticle-1 fade to the target opacity one after another
384 * @param[in] startIndex The index of the first particle
385 * @param[in] numParticle The number of particle to change opacity
386 * @param[in] targetValue The final opacity
387 * @param[in] duration The duration for the animation
389 void PlayParticleFadeAnimation(unsigned int startIndex, unsigned int numParticle, float targetValue, float duration)
391 if(GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f)
396 // start the opacity animation one particle after another gradually
397 float timeSlice = duration / (numParticle + 1);
398 float fadeDuration = timeSlice > 0.5f ? timeSlice : 0.5f;
400 Animation fadeAnimation = Animation::New(duration + fadeDuration * 2.f);
401 std::ostringstream oss;
402 for(unsigned int i = startIndex; i < numParticle; i++)
404 if(i >= NUM_PARTICLE) break; // out of bound
407 oss << OPACITY_UNIFORM_NAME << i << "]";
408 fadeAnimation.AnimateTo(Property(mEffect, oss.str()), targetValue, TimePeriod(timeSlice * i, fadeDuration * 2.f));
411 fadeAnimation.Play();
412 mFadeAnimation = fadeAnimation;
413 mFadeAnimation.FinishedSignal().Connect(this, &SparkleEffectExample::OnFadeAnimationFinished);
417 * Push the particles to the edge all around the circle then bounce back
418 * @param[in] duration The duration for the animation
419 * @param[in] tapPoint The position of the tap point
421 void PlayTapAnimation(float duration, const Vector2& tapPoint)
423 if(mTapIndices.y > mTapIndices.x && mTapAnimation.GetCurrentProgress() < 0.2f)
428 Animation animation = Animation::New(duration);
429 int idx = int(mTapIndices.y) % MAXIMUM_ANIMATION_COUNT;
430 mTapIndices.y += 1.f;
432 std::ostringstream oss;
433 oss << TAP_OFFSET_UNIFORM_NAME << idx << "]";
434 mEffect.SetProperty(mEffect.GetPropertyIndex(oss.str()), 0.f);
435 animation.AnimateTo(Property(mEffect, oss.str()), 0.75f, CustomBounce);
438 oss << TAP_POINT_UNIFORM_NAME << idx << "]";
439 mEffect.SetProperty(mEffect.GetPropertyIndex(oss.str()), tapPoint / ACTOR_SCALE);
441 mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
445 mTapAnimationAux = Animation::New(duration * 0.2f);
446 mTapAnimationAux.AnimateBy(Property(mEffect, ACCELARATION_UNIFORM_NAME), 0.15f, AlphaFunction::EASE_IN_OUT);
447 mTapAnimationAux.Play();
450 mTapAnimationIndexPair[animation] = static_cast<int>(mTapIndices.y - 1.f);
451 animation.FinishedSignal().Connect(this, &SparkleEffectExample::OnTapAnimationFinished);
452 mTapAnimation = animation;
456 * Callback of the animation finished signal
458 void OnShakeAnimationFinished(Animation& animation)
464 * Callback of the animation finished signal
466 void OnFadeAnimationFinished(Animation& animation)
468 mFadeAnimation.Clear();
469 mFadeAnimation.Reset();
473 * Callback of the animation finished signal
475 void OnBreakAnimationFinished(Animation& animation)
477 mEffect.SetProperty(mEffect.GetPropertyIndex(BREAK_UNIFORM_NAME), 0.f);
481 * Callback of the animation finished signal
483 void OnTapAnimationFinished(Animation& animation)
485 if(mTapAnimationIndexPair[animation] == static_cast<int>(mTapIndices.x))
487 mTapIndices.x += 1.f;
488 if(mTapIndices.x >= mTapIndices.y)
490 mTapIndices = Vector2::ZERO;
492 mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
495 mTapAnimationIndexPair.erase(animation);
496 if(mTapAnimationIndexPair.size() < 1 && mTapIndices != Vector2::ZERO)
498 mTapIndices = Vector2::ZERO;
499 mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
507 * Helper retrieve a uniform value from the Sparkle effect shader
508 * @param[in] uniformName The uniform
509 * @return The float value
511 float GetFloatUniformValue(const std::string& uniformName)
514 mEffect.GetProperty(mEffect.GetPropertyIndex(uniformName)).Get(value);
519 * Terminate the given animation
521 void DestroyAnimation(Animation& animation)
531 Application& mApplication;
533 ImageView mCircleBackground;
536 PanGestureDetector mPanGestureDetector;
537 TapGestureDetector mTapDetector;
539 Animation mFadeAnimation;
540 Animation mTapAnimation;
541 Animation mTapAnimationAux;
544 unsigned int mAnimationIndex;
547 std::map<Animation, int> mTapAnimationIndexPair;
550 int DALI_EXPORT_API main(int argc, char** argv)
552 Application application = Application::New(&argc, &argv);
553 SparkleEffectExample theApp(application);
554 application.MainLoop();