be899bec455bcf38d19238f46759c045b96b1d7f
[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/dali.h>
19 #include <dali-toolkit/dali-toolkit.h>
20
21 #include <sstream>
22 #include <algorithm>
23 #include <map>
24 #include <random>       // std::default_random_engine
25 #include <chrono>       // std::chrono::system_clock
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
38 //background image for normal status
39 const char * const CIRCLE_BACKGROUND_IMAGE( DEMO_IMAGE_DIR "sparkle_normal_background.png" );
40 //particle shape image
41 const char * const PARTICLE_IMAGE( DEMO_IMAGE_DIR "sparkle_particle.png" );
42
43 float EaseOutSquare( float progress )
44 {
45   return 1.0f - (1.0f-progress) * (1.0f-progress);
46 }
47
48 float CustomBounce( float progress )
49 {
50   float p = 1.f-progress;
51   p *=p;
52   return 17.68f*p*p*p*progress;
53 }
54
55 float Mix( const Vector2& range, float a )
56 {
57   return range.x * a + range.y*(1.f-a)-0.001f;
58 }
59
60 const Vector4 BACKGROUND_COLOR( 0.f, 0.f, 0.05f, 1.f );
61
62 } // unnamed namespace
63
64 // This example shows a sparkle particle effect
65 //
66 class SparkleEffectExample : public ConnectionTracker
67 {
68 public:
69
70   /**
71    * Create the SparkleEffectExample
72    * @param[in] application The DALi application instance
73    */
74   SparkleEffectExample( Application& application )
75   : mApplication( application ),
76     mAnimationIndex( 0u ),
77     mShaking( false )
78   {
79     mApplication.InitSignal().Connect( this, &SparkleEffectExample::OnInit );
80   }
81
82 private:
83
84   /**
85    * Initialize the SparkleEffectExample
86    * @param[in] application The DALi application instance
87    */
88   void OnInit( Application& application )
89   {
90     Window window = application.GetWindow();
91     window.KeyEventSignal().Connect(this, &SparkleEffectExample::OnKeyEvent);
92     window.SetBackgroundColor( BACKGROUND_COLOR );
93
94     mCircleBackground = ImageView::New( CIRCLE_BACKGROUND_IMAGE );
95     mCircleBackground.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
96     mCircleBackground.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER );
97
98     window.Add( mCircleBackground );
99
100     mEffect = SparkleEffect::New();
101
102     mMeshActor = CreateMeshActor();
103
104     window.Add( mMeshActor );
105
106     mMeshActor.SetProperty( Actor::Property::POSITION, ACTOR_POSITION );
107     mMeshActor.SetProperty( Actor::Property::SCALE, ACTOR_SCALE );
108
109     mTapDetector = TapGestureDetector::New();
110     mTapDetector.Attach(mCircleBackground);
111     mTapDetector.DetectedSignal().Connect( this, &SparkleEffectExample::OnTap );
112
113     mPanGestureDetector = PanGestureDetector::New();
114     mPanGestureDetector.DetectedSignal().Connect( this, &SparkleEffectExample::OnPan );
115     mPanGestureDetector.Attach( mCircleBackground );
116
117     PlayWanderAnimation( 35.f );
118   }
119
120   /**
121    * Create the mesh representing all the particles
122    */
123   Actor CreateMeshActor()
124   {
125     // shuffling to assign the color in random order
126     unsigned int* shuffleArray = new unsigned int[NUM_PARTICLE];
127     for( unsigned int i = 0; i<NUM_PARTICLE; i++ )
128     {
129       shuffleArray[i] = i;
130     }
131     const unsigned int seed = std::chrono::system_clock::now().time_since_epoch().count();
132     std::shuffle(&shuffleArray[0],&shuffleArray[NUM_PARTICLE], std::default_random_engine(seed));
133
134     // Create vertices
135
136     std::vector< Vertex > vertices;
137     std::vector< unsigned short > faces;
138
139     for( unsigned int i = 0; i<NUM_PARTICLE; i++ )
140     {
141       float colorIndex = GetColorIndex( shuffleArray[i] );
142       AddParticletoMesh( vertices, faces, PATHS[i], colorIndex );
143     }
144
145     delete [] shuffleArray;
146
147     Property::Map vertexFormat;
148     vertexFormat["aTexCoord"] = Property::VECTOR2;
149     vertexFormat["aParticlePath0"] = Property::VECTOR2;
150     vertexFormat["aParticlePath1"] = Property::VECTOR2;
151     vertexFormat["aParticlePath2"] = Property::VECTOR2;
152     vertexFormat["aParticlePath3"] = Property::VECTOR2;
153     vertexFormat["aParticlePath4"] = Property::VECTOR2;
154     vertexFormat["aParticlePath5"] = Property::VECTOR2;
155
156     VertexBuffer vertexBuffer = VertexBuffer::New( vertexFormat );
157     vertexBuffer.SetData( &vertices[0], vertices.size() );
158
159     Geometry geometry = Geometry::New();
160     geometry.AddVertexBuffer( vertexBuffer );
161     geometry.SetIndexBuffer( &faces[0], faces.size() );
162     geometry.SetType( Geometry::TRIANGLES );
163
164     Texture particleTexture = DemoHelper::LoadTexture( PARTICLE_IMAGE );
165     TextureSet textureSet = TextureSet::New();
166     textureSet.SetTexture( 0u, particleTexture );
167
168     Renderer renderer = Renderer::New( geometry, mEffect );
169     renderer.SetTextures( textureSet );
170
171     Actor meshActor = Actor::New();
172     meshActor.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
173     meshActor.SetProperty( Actor::Property::SIZE, Vector2( 1, 1 ) );
174     meshActor.AddRenderer( renderer );
175
176     return meshActor;
177   }
178
179   /**
180    * Defines a rule to assign particle with a color according to its index
181    */
182   float GetColorIndex( unsigned int particleIndex )
183   {
184     unsigned int thereshold = 0;
185     for( unsigned int i = 0; i<NUM_COLOR; i++ )
186     {
187       thereshold += PARTICLE_COLORS[i].numParticle;
188       if( particleIndex < thereshold)
189       {
190         return i + Mix( PARTICLE_COLORS[i].AlphaRange, static_cast<float>(thereshold-particleIndex)/PARTICLE_COLORS[i].numParticle );
191       }
192     }
193     return NUM_COLOR-1;
194   }
195
196   /**
197    * All a particle to the mesh by giving the moving path and color index
198    *
199    * Two triangles per particle
200    *  0---------3
201    *   |\      |
202    *   |  \    |
203    *   |    \  |
204    *   |      \|
205    *  1---------2
206    *
207    * The information we need to pass in through attribute include:
208    *
209    *   path which contains 12 integer
210    *          ---- passed in 6 Vector2 attributes
211    *
212    *   color index, particle index and textureCoor( (0,0) or (1,0) or (0,1) or (1,1)  )
213    *          ---- package these info into texCood attribute as: (+-colorIndex, +-particleIndex)
214    */
215   void AddParticletoMesh( std::vector< Vertex >& vertices,
216                           std::vector< unsigned short >& faces,
217                           MovingPath& movingPath,
218                           float colorIndex )
219   {
220     unsigned int idx = vertices.size();
221
222     // store the path into position and normal, which would be decoded inside the shader
223     Vector2 particlePath0( movingPath[0],  movingPath[1] );
224     Vector2 particlePath1( movingPath[2],  movingPath[3] );
225     Vector2 particlePath2( movingPath[4],  movingPath[5] );
226     Vector2 particlePath3( movingPath[6],  movingPath[7] );
227     Vector2 particlePath4( movingPath[8],  movingPath[9] );
228     Vector2 particlePath5( movingPath[10], movingPath[11] );
229
230     float particleIdx = static_cast<float>(idx/4 + 1); // count from 1
231     float colorIdx = colorIndex+1.f; // count from 1
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     vertices.push_back( Vertex( Vector2( colorIdx,  particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5 ) );
235     vertices.push_back( Vertex( Vector2( colorIdx, -particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5 ) );
236
237     faces.push_back(idx);
238     faces.push_back(idx+1);
239     faces.push_back(idx+2);
240
241     faces.push_back(idx);
242     faces.push_back(idx+2);
243     faces.push_back(idx+3);
244   }
245
246   /*
247    * Main key event handler
248    */
249   void OnKeyEvent(const KeyEvent& event)
250   {
251     if(event.state == KeyEvent::Down)
252     {
253       if( IsKey( event, Dali::DALI_KEY_ESCAPE) || IsKey( event, Dali::DALI_KEY_BACK) )
254       {
255         mApplication.Quit();
256       }
257     }
258   }
259
260   /**
261    * Callback of the TapGesture
262    */
263   void OnTap( Actor actor, const TapGesture& tap )
264   {
265     {
266       PlayTapAnimation(5.f, tap.localPoint);
267     }
268   }
269
270   /**
271    * Callback of the PanGesture
272    */
273   void OnPan( Actor actor, const PanGesture& gesture )
274   {
275     if( gesture.state == Gesture::Finished )
276     {
277       switch(mAnimationIndex)
278       {
279       case 0:
280       {
281         PlayParticleFadeAnimation(0, NUM_PARTICLE, 0.f, 3.f );
282         break;
283       }
284       case 1:
285       {
286         PlayBreakAnimation(2.0f);
287         break;
288       }
289       case 2:
290       {
291         PlayShakeAnimation(0.5f, 2.5f);
292         break;
293       }
294       default:
295       {
296         break;
297       }
298      }
299
300       mAnimationIndex = (mAnimationIndex+1)%3;
301     }
302   }
303
304   /**
305    * Animate the particle position to make them wandering on the screen with 'seemingly' random fade in/out
306    * @param[in] duration The duration for the particle to move a cycle on the path. the bigger this value the slower the floating movement.
307    * @param[in] looping Infinite playing or not
308    */
309   void PlayWanderAnimation( float duration, bool looping = true )
310   {
311     Animation wanderAnimation= Animation::New(duration);
312     wanderAnimation.AnimateTo( Property( mEffect, PERCENTAGE_UNIFORM_NAME ), 1.f );
313     wanderAnimation.SetLooping(looping); // infinite playing
314
315     wanderAnimation.Play();
316   }
317
318   /**
319    * Accelerate the particle moving speed
320    * @param[in] cycle How many extra cycles to move during the animation
321    * @param[in] duration The duration for the animation
322    */
323   void PlayShakeAnimation(  float cycle, float duration )
324   {
325     if( mShaking )
326     {
327       return;
328     }
329     DestroyAnimation( mTapAnimationAux );
330
331     float accelaration = GetFloatUniformValue( ACCELARATION_UNIFORM_NAME );
332     mEffect.SetProperty( mEffect.GetPropertyIndex(ACCELARATION_UNIFORM_NAME), accelaration - int( accelaration) ); // Set the value as its fractional part
333     Animation shakeAnimation = Animation::New(duration);
334     shakeAnimation.AnimateBy( Property( mEffect, ACCELARATION_UNIFORM_NAME ), cycle, AlphaFunction::EASE_OUT );
335     shakeAnimation.FinishedSignal().Connect( this, &SparkleEffectExample::OnShakeAnimationFinished );
336
337     shakeAnimation.Play();
338     mShaking = true;
339   }
340
341   /**
342    * Animate the particles to appear from center and spread all over around
343    * @param[in] duration The duration for the animation
344    */
345   void PlayBreakAnimation( float duration )
346   {
347     if( GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f )
348     {
349       return;
350     }
351
352     // Stop the fading / tap animation before the breaking
353     DestroyAnimation( mFadeAnimation);
354     mTapIndices.x = mTapIndices.y;
355     mEffect.SetProperty( mEffect.GetPropertyIndex( TAP_INDICES_UNIFORM_NAME ), mTapIndices );
356     mEffect.SetProperty( mEffect.GetPropertyIndex( ACCELARATION_UNIFORM_NAME ), 0.f );
357
358     // prepare the animation by setting the uniform to the required value
359     mEffect.SetProperty( mEffect.GetPropertyIndex( BREAK_UNIFORM_NAME ), 1.f );
360     mMeshActor.SetProperty( Actor::Property::SCALE,0.01f);
361     mEffect.SetProperty( mEffect.GetPropertyIndex( "uScale" ), 0.01f );
362     mMeshActor.SetProperty( Actor::Property::POSITION, Vector3( 0.f, 0.f, 1.f ) );
363
364     Animation breakAnimation = Animation::New(duration*1.5f);
365     breakAnimation.AnimateTo( Property(mMeshActor, Actor::Property::SCALE), Vector3(ACTOR_SCALE,ACTOR_SCALE,ACTOR_SCALE), EaseOutSquare);
366     breakAnimation.AnimateTo( Property( mEffect, "uScale" ), ACTOR_SCALE, EaseOutSquare);
367     breakAnimation.AnimateTo( Property(mMeshActor, Actor::Property::POSITION), ACTOR_POSITION, EaseOutSquare);
368     breakAnimation.FinishedSignal().Connect( this, &SparkleEffectExample::OnBreakAnimationFinished );
369
370     float timeUnit = duration/ (NUM_PARTICLE+1) /(NUM_PARTICLE+1) ;
371     std::ostringstream oss;
372     for(unsigned int i = 0; i<NUM_PARTICLE; i++)
373     {
374       oss.str("");
375       oss<< OPACITY_UNIFORM_NAME<< i << "]";
376       mEffect.SetProperty( mEffect.GetPropertyIndex( oss.str() ), 0.01f);
377       float timeSlice = timeUnit*i*i;
378       breakAnimation.AnimateTo( Property( mEffect, oss.str() ), 1.f, AlphaFunction::EASE_IN_OUT_SINE, TimePeriod( timeSlice*0.5f, timeSlice ) );
379     }
380
381     breakAnimation.Play();
382   }
383
384   /**
385    * Animate the particle opacity
386    * Particles with index between startIndex ~ startIndex+numParticle-1 fade to the target opacity one after another
387    * @param[in] startIndex The index of the first particle
388    * @param[in] numParticle The number of particle to change opacity
389    * @param[in] targetValue The final opacity
390    * @param[in] duration The duration for the animation
391    */
392   void PlayParticleFadeAnimation( unsigned int startIndex, unsigned int numParticle, float targetValue, float duration )
393   {
394     if( GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f )
395     {
396       return;
397     }
398
399     // start the opacity animation one particle after another gradually
400     float timeSlice = duration / (numParticle+1);
401     float fadeDuration = timeSlice>0.5f ? timeSlice : 0.5f;
402
403     Animation fadeAnimation= Animation::New(duration+fadeDuration*2.f);
404     std::ostringstream oss;
405     for(unsigned int i = startIndex; i<numParticle; i++)
406     {
407       if( i>=NUM_PARTICLE ) break; // out of bound
408
409       oss.str("");
410       oss<< OPACITY_UNIFORM_NAME<< i << "]";
411       fadeAnimation.AnimateTo(Property( mEffect, oss.str()), targetValue, TimePeriod( timeSlice*i, fadeDuration*2.f ));
412     }
413
414     fadeAnimation.Play();
415     mFadeAnimation = fadeAnimation;
416     mFadeAnimation.FinishedSignal().Connect( this, &SparkleEffectExample::OnFadeAnimationFinished );
417   }
418
419   /**
420    * Push the particles to the edge all around the circle then bounce back
421    * @param[in] duration The duration for the animation
422    * @param[in] tapPoint The position of the tap point
423    */
424   void PlayTapAnimation(float duration, const Vector2& tapPoint )
425   {
426     if( mTapIndices.y > mTapIndices.x && mTapAnimation.GetCurrentProgress() < 0.2f)
427     {
428       return;
429     }
430
431     Animation animation= Animation::New(duration);
432     int idx = int(mTapIndices.y)%MAXIMUM_ANIMATION_COUNT;
433     mTapIndices.y += 1.f;
434
435     std::ostringstream oss;
436     oss<< TAP_OFFSET_UNIFORM_NAME<< idx << "]";
437     mEffect.SetProperty( mEffect.GetPropertyIndex( oss.str() ), 0.f);
438     animation.AnimateTo( Property( mEffect, oss.str() ), 0.75f, CustomBounce);
439
440     oss.str("");
441     oss<< TAP_POINT_UNIFORM_NAME<< idx << "]";
442     mEffect.SetProperty( mEffect.GetPropertyIndex( oss.str() ), tapPoint/ACTOR_SCALE);
443
444     mEffect.SetProperty( mEffect.GetPropertyIndex( TAP_INDICES_UNIFORM_NAME ), mTapIndices);
445
446     if(!mShaking)
447     {
448       mTapAnimationAux = Animation::New(duration*0.2f);
449       mTapAnimationAux.AnimateBy( Property( mEffect, ACCELARATION_UNIFORM_NAME ), 0.15f, AlphaFunction::EASE_IN_OUT );
450       mTapAnimationAux.Play();
451     }
452     animation.Play();
453     mTapAnimationIndexPair[animation] = static_cast<int>(mTapIndices.y -1.f);
454     animation.FinishedSignal().Connect( this, &SparkleEffectExample::OnTapAnimationFinished );
455     mTapAnimation = animation;
456   }
457
458   /**
459    * Callback of the animation finished signal
460    */
461   void OnShakeAnimationFinished( Animation& animation)
462   {
463     mShaking = false;
464   }
465
466   /**
467    * Callback of the animation finished signal
468    */
469   void OnFadeAnimationFinished( Animation& animation)
470   {
471     mFadeAnimation.Clear();
472     mFadeAnimation.Reset();
473   }
474
475   /**
476    * Callback of the animation finished signal
477    */
478   void OnBreakAnimationFinished( Animation& animation)
479   {
480     mEffect.SetProperty( mEffect.GetPropertyIndex( BREAK_UNIFORM_NAME ), 0.f );
481   }
482
483   /**
484    * Callback of the animation finished signal
485    */
486   void OnTapAnimationFinished( Animation& animation )
487   {
488     if( mTapAnimationIndexPair[animation] ==  static_cast<int>(mTapIndices.x) )
489     {
490       mTapIndices.x += 1.f;
491       if( mTapIndices.x >= mTapIndices.y )
492       {
493         mTapIndices = Vector2::ZERO;
494       }
495       mEffect.SetProperty( mEffect.GetPropertyIndex( TAP_INDICES_UNIFORM_NAME ), mTapIndices);
496     }
497
498     mTapAnimationIndexPair.erase( animation );
499     if( mTapAnimationIndexPair.size() < 1 && mTapIndices != Vector2::ZERO)
500     {
501       mTapIndices = Vector2::ZERO;
502       mEffect.SetProperty( mEffect.GetPropertyIndex( TAP_INDICES_UNIFORM_NAME ), mTapIndices);
503     }
504
505     animation.Clear();
506     animation.Reset();
507   }
508
509   /**
510    * Helper retrieve a uniform value from the Sparkle effect shader
511    * @param[in] uniformName The uniform
512    * @return The float value
513    */
514   float GetFloatUniformValue( const std::string& uniformName )
515   {
516     float value;
517     mEffect.GetProperty(mEffect.GetPropertyIndex(uniformName)).Get(value);
518     return value;
519   }
520
521   /**
522    * Terminate the given animation
523    */
524   void DestroyAnimation( Animation& animation )
525   {
526     if( animation )
527     {
528       animation.Clear();
529       animation.Reset();
530     }
531   }
532
533 private:
534
535   Application&       mApplication;
536   Shader             mEffect;
537   ImageView          mCircleBackground;
538   Actor              mMeshActor;
539
540   PanGestureDetector mPanGestureDetector;
541   TapGestureDetector mTapDetector;
542
543   Animation          mFadeAnimation;
544   Animation          mTapAnimation;
545   Animation          mTapAnimationAux;
546
547   Vector2            mTapIndices;
548   unsigned int       mAnimationIndex;
549   bool               mShaking;
550
551   std::map< Animation, int > mTapAnimationIndexPair;
552 };
553
554 int DALI_EXPORT_API main( int argc, char **argv )
555 {
556   Application application = Application::New( &argc, &argv );
557   SparkleEffectExample theApp( application );
558   application.MainLoop();
559   return 0;
560 }