Updated demos to use DALi clang-format
[platform/core/uifw/dali-demo.git] / examples / sparkle / sparkle-effect-example.cpp
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 #include <dali-toolkit/dali-toolkit.h>
19 #include <dali/dali.h>
20
21 #include <algorithm>
22 #include <chrono> // std::chrono::system_clock
23 #include <map>
24 #include <random> // std::default_random_engine
25 #include <sstream>
26
27 #include "shared/utility.h"
28 #include "sparkle-effect.h"
29
30 using namespace Dali;
31 using Dali::Toolkit::ImageView;
32
33 using namespace SparkleEffect;
34
35 namespace // unnamed namespace
36 {
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");
41
42 float EaseOutSquare(float progress)
43 {
44   return 1.0f - (1.0f - progress) * (1.0f - progress);
45 }
46
47 float CustomBounce(float progress)
48 {
49   float p = 1.f - progress;
50   p *= p;
51   return 17.68f * p * p * p * progress;
52 }
53
54 float Mix(const Vector2& range, float a)
55 {
56   return range.x * a + range.y * (1.f - a) - 0.001f;
57 }
58
59 const Vector4 BACKGROUND_COLOR(0.f, 0.f, 0.05f, 1.f);
60
61 } // unnamed namespace
62
63 // This example shows a sparkle particle effect
64 //
65 class SparkleEffectExample : public ConnectionTracker
66 {
67 public:
68   /**
69    * Create the SparkleEffectExample
70    * @param[in] application The DALi application instance
71    */
72   SparkleEffectExample(Application& application)
73   : mApplication(application),
74     mAnimationIndex(0u),
75     mShaking(false)
76   {
77     mApplication.InitSignal().Connect(this, &SparkleEffectExample::OnInit);
78   }
79
80 private:
81   /**
82    * Initialize the SparkleEffectExample
83    * @param[in] application The DALi application instance
84    */
85   void OnInit(Application& application)
86   {
87     Window window = application.GetWindow();
88     window.KeyEventSignal().Connect(this, &SparkleEffectExample::OnKeyEvent);
89     window.SetBackgroundColor(BACKGROUND_COLOR);
90
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);
94
95     window.Add(mCircleBackground);
96
97     mEffect = SparkleEffect::New();
98
99     mMeshActor = CreateMeshActor();
100
101     window.Add(mMeshActor);
102
103     mMeshActor.SetProperty(Actor::Property::POSITION, ACTOR_POSITION);
104     mMeshActor.SetProperty(Actor::Property::SCALE, ACTOR_SCALE);
105
106     mTapDetector = TapGestureDetector::New();
107     mTapDetector.Attach(mCircleBackground);
108     mTapDetector.DetectedSignal().Connect(this, &SparkleEffectExample::OnTap);
109
110     mPanGestureDetector = PanGestureDetector::New();
111     mPanGestureDetector.DetectedSignal().Connect(this, &SparkleEffectExample::OnPan);
112     mPanGestureDetector.Attach(mCircleBackground);
113
114     PlayWanderAnimation(35.f);
115   }
116
117   /**
118    * Create the mesh representing all the particles
119    */
120   Actor CreateMeshActor()
121   {
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++)
125     {
126       shuffleArray[i] = i;
127     }
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));
130
131     // Create vertices
132
133     std::vector<Vertex>         vertices;
134     std::vector<unsigned short> faces;
135
136     for(unsigned int i = 0; i < NUM_PARTICLE; i++)
137     {
138       float colorIndex = GetColorIndex(shuffleArray[i]);
139       AddParticletoMesh(vertices, faces, PATHS[i], colorIndex);
140     }
141
142     delete[] shuffleArray;
143
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;
152
153     VertexBuffer vertexBuffer = VertexBuffer::New(vertexFormat);
154     vertexBuffer.SetData(&vertices[0], vertices.size());
155
156     Geometry geometry = Geometry::New();
157     geometry.AddVertexBuffer(vertexBuffer);
158     geometry.SetIndexBuffer(&faces[0], faces.size());
159     geometry.SetType(Geometry::TRIANGLES);
160
161     Texture    particleTexture = DemoHelper::LoadTexture(PARTICLE_IMAGE);
162     TextureSet textureSet      = TextureSet::New();
163     textureSet.SetTexture(0u, particleTexture);
164
165     Renderer renderer = Renderer::New(geometry, mEffect);
166     renderer.SetTextures(textureSet);
167
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);
172
173     return meshActor;
174   }
175
176   /**
177    * Defines a rule to assign particle with a color according to its index
178    */
179   float GetColorIndex(unsigned int particleIndex)
180   {
181     unsigned int thereshold = 0;
182     for(unsigned int i = 0; i < NUM_COLOR; i++)
183     {
184       thereshold += PARTICLE_COLORS[i].numParticle;
185       if(particleIndex < thereshold)
186       {
187         return i + Mix(PARTICLE_COLORS[i].AlphaRange, static_cast<float>(thereshold - particleIndex) / PARTICLE_COLORS[i].numParticle);
188       }
189     }
190     return NUM_COLOR - 1;
191   }
192
193   /**
194    * All a particle to the mesh by giving the moving path and color index
195    *
196    * Two triangles per particle
197    *  0---------3
198    *   |\      |
199    *   |  \    |
200    *   |    \  |
201    *   |      \|
202    *  1---------2
203    *
204    * The information we need to pass in through attribute include:
205    *
206    *   path which contains 12 integer
207    *          ---- passed in 6 Vector2 attributes
208    *
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)
211    */
212   void AddParticletoMesh(std::vector<Vertex>&         vertices,
213                          std::vector<unsigned short>& faces,
214                          MovingPath&                  movingPath,
215                          float                        colorIndex)
216   {
217     unsigned int idx = vertices.size();
218
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]);
226
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));
233
234     faces.push_back(idx);
235     faces.push_back(idx + 1);
236     faces.push_back(idx + 2);
237
238     faces.push_back(idx);
239     faces.push_back(idx + 2);
240     faces.push_back(idx + 3);
241   }
242
243   /*
244    * Main key event handler
245    */
246   void OnKeyEvent(const KeyEvent& event)
247   {
248     if(event.GetState() == KeyEvent::DOWN)
249     {
250       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
251       {
252         mApplication.Quit();
253       }
254     }
255   }
256
257   /**
258    * Callback of the TapGesture
259    */
260   void OnTap(Actor actor, const TapGesture& tap)
261   {
262     {
263       PlayTapAnimation(5.f, tap.GetLocalPoint());
264     }
265   }
266
267   /**
268    * Callback of the PanGesture
269    */
270   void OnPan(Actor actor, const PanGesture& gesture)
271   {
272     if(gesture.GetState() == GestureState::FINISHED)
273     {
274       switch(mAnimationIndex)
275       {
276         case 0:
277         {
278           PlayParticleFadeAnimation(0, NUM_PARTICLE, 0.f, 3.f);
279           break;
280         }
281         case 1:
282         {
283           PlayBreakAnimation(2.0f);
284           break;
285         }
286         case 2:
287         {
288           PlayShakeAnimation(0.5f, 2.5f);
289           break;
290         }
291         default:
292         {
293           break;
294         }
295       }
296
297       mAnimationIndex = (mAnimationIndex + 1) % 3;
298     }
299   }
300
301   /**
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
305    */
306   void PlayWanderAnimation(float duration, bool looping = true)
307   {
308     Animation wanderAnimation = Animation::New(duration);
309     wanderAnimation.AnimateTo(Property(mEffect, PERCENTAGE_UNIFORM_NAME), 1.f);
310     wanderAnimation.SetLooping(looping); // infinite playing
311
312     wanderAnimation.Play();
313   }
314
315   /**
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
319    */
320   void PlayShakeAnimation(float cycle, float duration)
321   {
322     if(mShaking)
323     {
324       return;
325     }
326     DestroyAnimation(mTapAnimationAux);
327
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);
333
334     shakeAnimation.Play();
335     mShaking = true;
336   }
337
338   /**
339    * Animate the particles to appear from center and spread all over around
340    * @param[in] duration The duration for the animation
341    */
342   void PlayBreakAnimation(float duration)
343   {
344     if(GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f)
345     {
346       return;
347     }
348
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);
354
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));
360
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);
366
367     float              timeUnit = duration / (NUM_PARTICLE + 1) / (NUM_PARTICLE + 1);
368     std::ostringstream oss;
369     for(unsigned int i = 0; i < NUM_PARTICLE; i++)
370     {
371       oss.str("");
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));
376     }
377
378     breakAnimation.Play();
379   }
380
381   /**
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
388    */
389   void PlayParticleFadeAnimation(unsigned int startIndex, unsigned int numParticle, float targetValue, float duration)
390   {
391     if(GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f)
392     {
393       return;
394     }
395
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;
399
400     Animation          fadeAnimation = Animation::New(duration + fadeDuration * 2.f);
401     std::ostringstream oss;
402     for(unsigned int i = startIndex; i < numParticle; i++)
403     {
404       if(i >= NUM_PARTICLE) break; // out of bound
405
406       oss.str("");
407       oss << OPACITY_UNIFORM_NAME << i << "]";
408       fadeAnimation.AnimateTo(Property(mEffect, oss.str()), targetValue, TimePeriod(timeSlice * i, fadeDuration * 2.f));
409     }
410
411     fadeAnimation.Play();
412     mFadeAnimation = fadeAnimation;
413     mFadeAnimation.FinishedSignal().Connect(this, &SparkleEffectExample::OnFadeAnimationFinished);
414   }
415
416   /**
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
420    */
421   void PlayTapAnimation(float duration, const Vector2& tapPoint)
422   {
423     if(mTapIndices.y > mTapIndices.x && mTapAnimation.GetCurrentProgress() < 0.2f)
424     {
425       return;
426     }
427
428     Animation animation = Animation::New(duration);
429     int       idx       = int(mTapIndices.y) % MAXIMUM_ANIMATION_COUNT;
430     mTapIndices.y += 1.f;
431
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);
436
437     oss.str("");
438     oss << TAP_POINT_UNIFORM_NAME << idx << "]";
439     mEffect.SetProperty(mEffect.GetPropertyIndex(oss.str()), tapPoint / ACTOR_SCALE);
440
441     mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
442
443     if(!mShaking)
444     {
445       mTapAnimationAux = Animation::New(duration * 0.2f);
446       mTapAnimationAux.AnimateBy(Property(mEffect, ACCELARATION_UNIFORM_NAME), 0.15f, AlphaFunction::EASE_IN_OUT);
447       mTapAnimationAux.Play();
448     }
449     animation.Play();
450     mTapAnimationIndexPair[animation] = static_cast<int>(mTapIndices.y - 1.f);
451     animation.FinishedSignal().Connect(this, &SparkleEffectExample::OnTapAnimationFinished);
452     mTapAnimation = animation;
453   }
454
455   /**
456    * Callback of the animation finished signal
457    */
458   void OnShakeAnimationFinished(Animation& animation)
459   {
460     mShaking = false;
461   }
462
463   /**
464    * Callback of the animation finished signal
465    */
466   void OnFadeAnimationFinished(Animation& animation)
467   {
468     mFadeAnimation.Clear();
469     mFadeAnimation.Reset();
470   }
471
472   /**
473    * Callback of the animation finished signal
474    */
475   void OnBreakAnimationFinished(Animation& animation)
476   {
477     mEffect.SetProperty(mEffect.GetPropertyIndex(BREAK_UNIFORM_NAME), 0.f);
478   }
479
480   /**
481    * Callback of the animation finished signal
482    */
483   void OnTapAnimationFinished(Animation& animation)
484   {
485     if(mTapAnimationIndexPair[animation] == static_cast<int>(mTapIndices.x))
486     {
487       mTapIndices.x += 1.f;
488       if(mTapIndices.x >= mTapIndices.y)
489       {
490         mTapIndices = Vector2::ZERO;
491       }
492       mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
493     }
494
495     mTapAnimationIndexPair.erase(animation);
496     if(mTapAnimationIndexPair.size() < 1 && mTapIndices != Vector2::ZERO)
497     {
498       mTapIndices = Vector2::ZERO;
499       mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
500     }
501
502     animation.Clear();
503     animation.Reset();
504   }
505
506   /**
507    * Helper retrieve a uniform value from the Sparkle effect shader
508    * @param[in] uniformName The uniform
509    * @return The float value
510    */
511   float GetFloatUniformValue(const std::string& uniformName)
512   {
513     float value;
514     mEffect.GetProperty(mEffect.GetPropertyIndex(uniformName)).Get(value);
515     return value;
516   }
517
518   /**
519    * Terminate the given animation
520    */
521   void DestroyAnimation(Animation& animation)
522   {
523     if(animation)
524     {
525       animation.Clear();
526       animation.Reset();
527     }
528   }
529
530 private:
531   Application& mApplication;
532   Shader       mEffect;
533   ImageView    mCircleBackground;
534   Actor        mMeshActor;
535
536   PanGestureDetector mPanGestureDetector;
537   TapGestureDetector mTapDetector;
538
539   Animation mFadeAnimation;
540   Animation mTapAnimation;
541   Animation mTapAnimationAux;
542
543   Vector2      mTapIndices;
544   unsigned int mAnimationIndex;
545   bool         mShaking;
546
547   std::map<Animation, int> mTapAnimationIndexPair;
548 };
549
550 int DALI_EXPORT_API main(int argc, char** argv)
551 {
552   Application          application = Application::New(&argc, &argv);
553   SparkleEffectExample theApp(application);
554   application.MainLoop();
555   return 0;
556 }