(Sparkles) Fixed issue with partial rendering not animating
[platform/core/uifw/dali-demo.git] / examples / sparkle / sparkle-effect-example.cpp
1 /*
2  * Copyright (c) 2023 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.SetProperty(Actor::Property::UPDATE_AREA_HINT, ACTOR_UPDATE_AREA_HINT);
172     meshActor.AddRenderer(renderer);
173
174     return meshActor;
175   }
176
177   /**
178    * Defines a rule to assign particle with a color according to its index
179    */
180   float GetColorIndex(unsigned int particleIndex)
181   {
182     unsigned int thereshold = 0;
183     for(unsigned int i = 0; i < NUM_COLOR; i++)
184     {
185       thereshold += PARTICLE_COLORS[i].numParticle;
186       if(particleIndex < thereshold)
187       {
188         return i + Mix(PARTICLE_COLORS[i].AlphaRange, static_cast<float>(thereshold - particleIndex) / PARTICLE_COLORS[i].numParticle);
189       }
190     }
191     return NUM_COLOR - 1;
192   }
193
194   /**
195    * All a particle to the mesh by giving the moving path and color index
196    *
197    * Two triangles per particle
198    *  0---------3
199    *   |\      |
200    *   |  \    |
201    *   |    \  |
202    *   |      \|
203    *  1---------2
204    *
205    * The information we need to pass in through attribute include:
206    *
207    *   path which contains 12 integer
208    *          ---- passed in 6 Vector2 attributes
209    *
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)
212    */
213   void AddParticletoMesh(std::vector<Vertex>&         vertices,
214                          std::vector<unsigned short>& faces,
215                          MovingPath&                  movingPath,
216                          float                        colorIndex)
217   {
218     unsigned int idx = vertices.size();
219
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]);
227
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});
234
235     faces.push_back(idx);
236     faces.push_back(idx + 1);
237     faces.push_back(idx + 2);
238
239     faces.push_back(idx);
240     faces.push_back(idx + 2);
241     faces.push_back(idx + 3);
242   }
243
244   /*
245    * Main key event handler
246    */
247   void OnKeyEvent(const KeyEvent& event)
248   {
249     if(event.GetState() == KeyEvent::DOWN)
250     {
251       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
252       {
253         mApplication.Quit();
254       }
255     }
256   }
257
258   /**
259    * Callback of the TapGesture
260    */
261   void OnTap(Actor actor, const TapGesture& tap)
262   {
263     {
264       PlayTapAnimation(5.f, tap.GetLocalPoint());
265     }
266   }
267
268   /**
269    * Callback of the PanGesture
270    */
271   void OnPan(Actor actor, const PanGesture& gesture)
272   {
273     if(gesture.GetState() == GestureState::FINISHED)
274     {
275       switch(mAnimationIndex)
276       {
277         case 0:
278         {
279           PlayParticleFadeAnimation(0, NUM_PARTICLE, 0.f, 3.f);
280           break;
281         }
282         case 1:
283         {
284           PlayBreakAnimation(2.0f);
285           break;
286         }
287         case 2:
288         {
289           PlayShakeAnimation(0.5f, 2.5f);
290           break;
291         }
292         default:
293         {
294           break;
295         }
296       }
297
298       mAnimationIndex = (mAnimationIndex + 1) % 3;
299     }
300   }
301
302   /**
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
306    */
307   void PlayWanderAnimation(float duration, bool looping = true)
308   {
309     Animation wanderAnimation = Animation::New(duration);
310     wanderAnimation.AnimateTo(Property(mEffect, PERCENTAGE_UNIFORM_NAME), 1.f);
311     wanderAnimation.SetLooping(looping); // infinite playing
312
313     wanderAnimation.Play();
314   }
315
316   /**
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
320    */
321   void PlayShakeAnimation(float cycle, float duration)
322   {
323     if(mShaking)
324     {
325       return;
326     }
327     DestroyAnimation(mTapAnimationAux);
328
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);
334
335     shakeAnimation.Play();
336     mShaking = true;
337   }
338
339   /**
340    * Animate the particles to appear from center and spread all over around
341    * @param[in] duration The duration for the animation
342    */
343   void PlayBreakAnimation(float duration)
344   {
345     if(GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f)
346     {
347       return;
348     }
349
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);
355
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));
361
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);
367
368     float              timeUnit = duration / (NUM_PARTICLE + 1) / (NUM_PARTICLE + 1);
369     std::ostringstream oss;
370     for(unsigned int i = 0; i < NUM_PARTICLE; i++)
371     {
372       oss.str("");
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));
377     }
378
379     breakAnimation.Play();
380   }
381
382   /**
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
389    */
390   void PlayParticleFadeAnimation(unsigned int startIndex, unsigned int numParticle, float targetValue, float duration)
391   {
392     if(GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f)
393     {
394       return;
395     }
396
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;
400
401     Animation          fadeAnimation = Animation::New(duration + fadeDuration * 2.f);
402     std::ostringstream oss;
403     for(unsigned int i = startIndex; i < numParticle; i++)
404     {
405       if(i >= NUM_PARTICLE) break; // out of bound
406
407       oss.str("");
408       oss << OPACITY_UNIFORM_NAME << i << "]";
409       fadeAnimation.AnimateTo(Property(mEffect, oss.str()), targetValue, TimePeriod(timeSlice * i, fadeDuration * 2.f));
410     }
411
412     fadeAnimation.Play();
413     mFadeAnimation = fadeAnimation;
414     mFadeAnimation.FinishedSignal().Connect(this, &SparkleEffectExample::OnFadeAnimationFinished);
415   }
416
417   /**
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
421    */
422   void PlayTapAnimation(float duration, const Vector2& tapPoint)
423   {
424     if(mTapIndices.y > mTapIndices.x && mTapAnimation.GetCurrentProgress() < 0.2f)
425     {
426       return;
427     }
428
429     Animation animation = Animation::New(duration);
430     int       idx       = int(mTapIndices.y) % MAXIMUM_ANIMATION_COUNT;
431     mTapIndices.y += 1.f;
432
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);
437
438     oss.str("");
439     oss << TAP_POINT_UNIFORM_NAME << idx << "]";
440     mEffect.SetProperty(mEffect.GetPropertyIndex(oss.str()), tapPoint / ACTOR_SCALE);
441
442     mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
443
444     if(!mShaking)
445     {
446       mTapAnimationAux = Animation::New(duration * 0.2f);
447       mTapAnimationAux.AnimateBy(Property(mEffect, ACCELARATION_UNIFORM_NAME), 0.15f, AlphaFunction::EASE_IN_OUT);
448       mTapAnimationAux.Play();
449     }
450     animation.Play();
451     mTapAnimationIndexPair[animation] = static_cast<int>(mTapIndices.y - 1.f);
452     animation.FinishedSignal().Connect(this, &SparkleEffectExample::OnTapAnimationFinished);
453     mTapAnimation = animation;
454   }
455
456   /**
457    * Callback of the animation finished signal
458    */
459   void OnShakeAnimationFinished(Animation& animation)
460   {
461     mShaking = false;
462   }
463
464   /**
465    * Callback of the animation finished signal
466    */
467   void OnFadeAnimationFinished(Animation& animation)
468   {
469     mFadeAnimation.Clear();
470     mFadeAnimation.Reset();
471   }
472
473   /**
474    * Callback of the animation finished signal
475    */
476   void OnBreakAnimationFinished(Animation& animation)
477   {
478     mEffect.SetProperty(mEffect.GetPropertyIndex(BREAK_UNIFORM_NAME), 0.f);
479   }
480
481   /**
482    * Callback of the animation finished signal
483    */
484   void OnTapAnimationFinished(Animation& animation)
485   {
486     if(mTapAnimationIndexPair[animation] == static_cast<int>(mTapIndices.x))
487     {
488       mTapIndices.x += 1.f;
489       if(mTapIndices.x >= mTapIndices.y)
490       {
491         mTapIndices = Vector2::ZERO;
492       }
493       mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
494     }
495
496     mTapAnimationIndexPair.erase(animation);
497     if(mTapAnimationIndexPair.size() < 1 && mTapIndices != Vector2::ZERO)
498     {
499       mTapIndices = Vector2::ZERO;
500       mEffect.SetProperty(mEffect.GetPropertyIndex(TAP_INDICES_UNIFORM_NAME), mTapIndices);
501     }
502
503     animation.Clear();
504     animation.Reset();
505   }
506
507   /**
508    * Helper retrieve a uniform value from the Sparkle effect shader
509    * @param[in] uniformName The uniform
510    * @return The float value
511    */
512   float GetFloatUniformValue(const std::string& uniformName)
513   {
514     float value;
515     mEffect.GetProperty(mEffect.GetPropertyIndex(uniformName)).Get(value);
516     return value;
517   }
518
519   /**
520    * Terminate the given animation
521    */
522   void DestroyAnimation(Animation& animation)
523   {
524     if(animation)
525     {
526       animation.Clear();
527       animation.Reset();
528     }
529   }
530
531 private:
532   Application& mApplication;
533   Shader       mEffect;
534   ImageView    mCircleBackground;
535   Actor        mMeshActor;
536
537   PanGestureDetector mPanGestureDetector;
538   TapGestureDetector mTapDetector;
539
540   Animation mFadeAnimation;
541   Animation mTapAnimation;
542   Animation mTapAnimationAux;
543
544   Vector2      mTapIndices;
545   unsigned int mAnimationIndex;
546   bool         mShaking;
547
548   std::map<Animation, int> mTapAnimationIndexPair;
549 };
550
551 int DALI_EXPORT_API main(int argc, char** argv)
552 {
553   Application          application = Application::New(&argc, &argv);
554   SparkleEffectExample theApp(application);
555   application.MainLoop();
556   return 0;
557 }