[dali_2.3.22] Merge branch 'devel/master'
[platform/core/uifw/dali-demo.git] / examples / metaball-explosion / metaball-explosion-example.cpp
1 /*
2  * Copyright (c) 2021 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 // EXTERNAL INCLUDES
19 #include <cstdint> // uint32_t, uint16_t etc
20 #include <cstdio>
21 #include <string>
22
23 #include <dali/public-api/math/random.h>
24 #include <dali/public-api/rendering/frame-buffer.h>
25 #include <dali/public-api/rendering/renderer.h>
26 #include <dali/public-api/rendering/texture-set.h>
27 #include <dali/public-api/rendering/texture.h>
28
29 // INTERNAL INCLUDES
30 #include "generated/metaball-frag.h"
31 #include "generated/metaball-refraction-frag.h"
32 #include "generated/metaball-vert.h"
33 #include "shared/utility.h" // DemoHelper::LoadTexture
34
35 using namespace Dali;
36
37 namespace // unnamed namespace for constants
38 {
39 // background image
40 const char* const BACKGROUND_IMAGE(DEMO_IMAGE_DIR "background-2.jpg");
41
42 // number of metaballs
43 constexpr uint32_t METABALL_NUMBER = 6;
44
45 /**
46  * Metadata for each ball
47  */
48 struct MetaballInfo
49 {
50   Actor   actor;
51   Vector2 position;
52   float   radius;
53   float   initRadius;
54
55   //new shader stuff
56   Property::Index positionIndex;
57   Property::Index positionVarIndex;
58 };
59
60 } // unnamed namespace
61
62 /**
63  * Demo using Metaballs
64  *
65  * When the metaball is clicked it explodes to smaller balls
66  */
67 class MetaballExplosionController : public ConnectionTracker
68 {
69 public:
70   /**
71    * Constructor
72    * @param application
73    */
74   MetaballExplosionController(Application& application);
75
76   /**
77    * Destructor
78    */
79   virtual ~MetaballExplosionController();
80
81   /**
82    * Creates the metaballs and initializes the scene
83    */
84   void Create(Application& app);
85
86   /**
87    * Touch event handler to center metaballs at touch position
88    * and start explosion animation on release
89    */
90   bool OnTouch(Actor actor, const TouchEvent& touch);
91
92   /**
93    * Key event handler to quit application on escape or back key
94    */
95   void OnKeyEvent(const KeyEvent& event);
96
97 private: // Data
98   Application& mApplication;
99   Vector2      mScreenSize;
100
101   Texture     mBackgroundTexture;
102   FrameBuffer mMetaballFBO;
103
104   Actor        mMetaballRoot;
105   MetaballInfo mMetaballs[METABALL_NUMBER];
106
107   Property::Index mPositionIndex;
108   Actor           mCompositionActor;
109
110   //Motion
111   Vector2 mCurrentTouchPosition;
112   Vector2 mMetaballPosVariation;
113   Vector2 mMetaballPosVariationFrom;
114   Vector2 mMetaballPosVariationTo;
115   Vector2 mMetaballCenter;
116
117   //Animations
118   Animation mPositionVarAnimation[METABALL_NUMBER];
119
120   uint32_t  mDispersion;
121   Animation mDispersionAnimation[METABALL_NUMBER];
122
123   Timer mTimerDispersion;
124
125   float mTimeMultiplier;
126
127   // Private helper functions
128
129   /**
130    * Create a mesh data with the geometry for the metaball rendering
131    * @param aspectMappedTexture whether texture coords should be mapped based on aspect ratio
132    */
133   Geometry CreateGeometry(bool aspectMappedTexture = true);
134
135   /**
136    * Create a actors and renderers for the metaballs
137    */
138   void CreateMetaballActors();
139
140   /**
141    * Create the render task and FBO to render the metaballs into a texture
142    */
143   void CreateMetaballImage();
144
145   /**
146    * Create the the final composition
147    */
148   void CreateComposition();
149
150   /**
151    * Function to create animations for the small variations of position inside the metaball
152    */
153   void CreateAnimations();
154
155   /**
156    * Function to reset metaball state
157    */
158   void ResetMetaballs(bool resetAnims);
159
160   /**
161    * Function to create disperse each of the ball that compose the metaball when exploding
162    */
163   void DisperseBallAnimation(uint32_t ball);
164
165   /**
166    * Function to make metaballs come back to reset position
167    */
168   void LaunchResetMetaballPosition(Animation& source);
169
170   /**
171    * Function to set things at the end of the animation
172    */
173   void EndDisperseAnimation(Animation& source);
174
175   /**
176    * Function to init dispersion of the metaballs one by one using a timer
177    * (so not all the balls begin moving at the same time)
178    */
179   bool OnTimerDispersionTick();
180
181   /**
182    * Function to set the actual position of the metaballs when the user clicks the screen
183    */
184   void SetPositionToMetaballs(const Vector2& metaballCenter);
185 };
186
187 /**
188  * Implementation
189  */
190
191 MetaballExplosionController::MetaballExplosionController(Application& application)
192 : mApplication(application),
193   mScreenSize(),
194   mBackgroundTexture(),
195   mMetaballFBO(),
196   mMetaballRoot(),
197   mMetaballs(),
198   mPositionIndex(),
199   mCompositionActor(),
200   mCurrentTouchPosition(),
201   mMetaballPosVariation(),
202   mMetaballPosVariationFrom(),
203   mMetaballPosVariationTo(),
204   mMetaballCenter(),
205   mPositionVarAnimation(),
206   mDispersion(0),
207   mDispersionAnimation(),
208   mTimerDispersion(),
209   mTimeMultiplier(1.0f)
210 {
211   // Connect to the Application's Init signal
212   mApplication.InitSignal().Connect(this, &MetaballExplosionController::Create);
213 }
214
215 MetaballExplosionController::~MetaballExplosionController()
216 {
217   // Nothing to do here;
218 }
219
220 void MetaballExplosionController::Create(Application& app)
221 {
222   Window window = app.GetWindow();
223
224   window.KeyEventSignal().Connect(this, &MetaballExplosionController::OnKeyEvent);
225
226   mScreenSize = window.GetSize();
227
228   mTimeMultiplier = 1.0f;
229
230   window.SetBackgroundColor(Color::BLACK);
231
232   // Load background texture
233   mBackgroundTexture = DemoHelper::LoadTexture(BACKGROUND_IMAGE);
234
235   srand(static_cast<uint32_t>(time(0)));
236
237   //Create internal data
238   CreateMetaballActors();
239   CreateMetaballImage();
240   CreateComposition();
241
242   CreateAnimations();
243
244   mDispersion      = 0;
245   mTimerDispersion = Timer::New(150);
246   mTimerDispersion.TickSignal().Connect(this, &MetaballExplosionController::OnTimerDispersionTick);
247
248   // Connect the callback to the touch signal on the mesh actor
249   window.GetRootLayer().TouchedSignal().Connect(this, &MetaballExplosionController::OnTouch);
250 }
251
252 Geometry MetaballExplosionController::CreateGeometry(bool aspectMappedTexture)
253 {
254   const float aspect = mScreenSize.y / mScreenSize.x;
255
256   // Create vertices and specify their color
257   const float xsize = mScreenSize.x * 0.5;
258
259   // Create the meshdata for the metaballs
260   struct VertexPosition
261   {
262     Vector2 position;
263   };
264   struct VertexTexture
265   {
266     Vector2 texture;
267   };
268
269   VertexPosition vertices[] =
270     {
271       {Vector2(-xsize, -xsize * aspect)},
272       {Vector2(xsize, -xsize * aspect)},
273       {Vector2(-xsize, xsize * aspect)},
274       {Vector2(xsize, xsize * aspect)}};
275
276   const float   textureAspect = (aspectMappedTexture) ? aspect : 1.0f;
277   VertexTexture textures[] =
278     {
279       {Vector2(0.0f, 0.0f)},
280       {Vector2(1.0f, 0.0f)},
281       {Vector2(0.0f, 1.0f * textureAspect)},
282       {Vector2(1.0f, 1.0f * textureAspect)}};
283
284   uint32_t numberOfVertices = sizeof(vertices) / sizeof(VertexPosition);
285
286   // Vertices
287   Property::Map positionVertexFormat;
288   positionVertexFormat["aPosition"] = Property::VECTOR2;
289   VertexBuffer positionVertices     = VertexBuffer::New(positionVertexFormat);
290   positionVertices.SetData(vertices, numberOfVertices);
291
292   // Textures
293   Property::Map textureVertexFormat;
294   textureVertexFormat["aTexture"] = Property::VECTOR2;
295   VertexBuffer textureVertices    = VertexBuffer::New(textureVertexFormat);
296   textureVertices.SetData(textures, numberOfVertices);
297
298   // Indices
299   const uint16_t indices[] = {0, 3, 1, 0, 2, 3};
300
301   // Create the geometry object
302   Geometry texturedQuadGeometry = Geometry::New();
303   texturedQuadGeometry.AddVertexBuffer(positionVertices);
304   texturedQuadGeometry.AddVertexBuffer(textureVertices);
305
306   texturedQuadGeometry.SetIndexBuffer(&indices[0], sizeof(indices) / sizeof(indices[0]));
307
308   return texturedQuadGeometry;
309 }
310
311 void MetaballExplosionController::CreateMetaballActors()
312 {
313   // Create the shader for the metaballs, tell DALi that shader modifies geometry so we dont need to set a meaningless size
314   Shader shader = Shader::New(SHADER_METABALL_VERT, SHADER_METABALL_FRAG, Shader::Hint::MODIFIES_GEOMETRY);
315
316   Geometry metaballGeom = CreateGeometry();
317   // Reuse same renderer for each actor
318   Renderer renderer = Renderer::New(metaballGeom, shader);
319   renderer.SetProperty(Renderer::Property::BLEND_MODE, BlendMode::ON);
320   renderer.SetProperty(Renderer::Property::BLEND_FACTOR_SRC_RGB, BlendFactor::ONE);
321   renderer.SetProperty(Renderer::Property::BLEND_FACTOR_DEST_RGB, BlendFactor::ONE);
322   renderer.SetProperty(Renderer::Property::BLEND_FACTOR_SRC_ALPHA, BlendFactor::ONE);
323   renderer.SetProperty(Renderer::Property::BLEND_FACTOR_DEST_ALPHA, BlendFactor::ONE);
324
325   //Initialization of each of the metaballs
326   for(uint32_t i = 0; i < METABALL_NUMBER; i++)
327   {
328     mMetaballs[i].position = Vector2(0.0f, 0.0f);
329     mMetaballs[i].radius = mMetaballs[i].initRadius = Random::Range(0.05f, 0.07f);
330
331     mMetaballs[i].actor = Actor::New();
332     mMetaballs[i].actor.SetProperty(Dali::Actor::Property::NAME, "Metaball");
333     mMetaballs[i].actor.SetProperty(Actor::Property::SCALE, 1.0f);
334     mMetaballs[i].actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
335     mMetaballs[i].actor.AddRenderer(renderer);
336
337     mMetaballs[i].positionIndex = mMetaballs[i].actor.RegisterProperty("uPositionMetaball", mMetaballs[i].position);
338
339     mMetaballs[i].positionVarIndex = mMetaballs[i].actor.RegisterProperty("uPositionVar", Vector2(0.f, 0.f));
340
341     mMetaballs[i].actor.RegisterProperty("uGravityVector", Vector2(Random::Range(-0.2, 0.2), Random::Range(-0.2, 0.2)));
342     mMetaballs[i].actor.RegisterProperty("uRadius", mMetaballs[i].radius);
343     mMetaballs[i].actor.RegisterProperty("uRadiusVar", 0.f);
344   }
345
346   // Root creation
347   mMetaballRoot = Actor::New();
348   mMetaballRoot.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
349   for(uint32_t i = 0; i < METABALL_NUMBER; i++)
350   {
351     mMetaballRoot.Add(mMetaballs[i].actor);
352   }
353 }
354
355 void MetaballExplosionController::CreateMetaballImage()
356 {
357   // Create an FBO and a render task to create to render the metaballs with a fragment shader
358   Window window = mApplication.GetWindow();
359
360   mMetaballFBO = FrameBuffer::New(mScreenSize.x, mScreenSize.y);
361
362   window.Add(mMetaballRoot);
363
364   // Create the render task used to render the metaballs
365   RenderTaskList taskList = window.GetRenderTaskList();
366   RenderTask     task     = taskList.CreateTask();
367   task.SetRefreshRate(RenderTask::REFRESH_ALWAYS);
368   task.SetSourceActor(mMetaballRoot);
369   task.SetExclusive(true);
370   task.SetClearColor(Color::BLACK);
371   task.SetClearEnabled(true);
372   task.SetFrameBuffer(mMetaballFBO);
373 }
374
375 void MetaballExplosionController::CreateComposition()
376 {
377   //Create new shader
378   Shader shader = Shader::New(SHADER_METABALL_VERT, SHADER_METABALL_REFRACTION_FRAG);
379
380   // Create new texture set
381   auto textureSet = TextureSet::New();
382   textureSet.SetTexture(0u, mBackgroundTexture);
383   textureSet.SetTexture(1u, mMetaballFBO.GetColorTexture());
384
385   // Create geometry
386   Geometry metaballGeom = CreateGeometry(false);
387
388   Renderer mRenderer = Renderer::New(metaballGeom, shader);
389   mRenderer.SetTextures(textureSet);
390
391   // Create actor
392   mCompositionActor = Actor::New();
393   mCompositionActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
394   mCompositionActor.SetProperty(Actor::Property::POSITION, Vector3(0.0f, 0.0f, 0.0f));
395   mCompositionActor.SetProperty(Actor::Property::SIZE, Vector2(mScreenSize.x, mScreenSize.y));
396   mCompositionActor.AddRenderer(mRenderer);
397
398   Vector2 metaballCenter(0.0, 0);
399   metaballCenter.x = metaballCenter.x * 0.5;
400   metaballCenter.y = metaballCenter.y * 0.5;
401   mPositionIndex   = mCompositionActor.RegisterProperty("uPositionMetaball", metaballCenter);
402
403   SetPositionToMetaballs(metaballCenter);
404
405   mCompositionActor.SetProperty(Actor::Property::SIZE, Vector2(mScreenSize.x, mScreenSize.y));
406
407   Window window = mApplication.GetWindow();
408   window.Add(mCompositionActor);
409 }
410
411 void MetaballExplosionController::CreateAnimations()
412 {
413   Vector2 direction;
414
415   for(uint32_t i = 0; i < METABALL_NUMBER; i++)
416   {
417     KeyFrames keySinCosVariation = KeyFrames::New();
418     Vector2   sinCosVariation(0, 0);
419
420     direction.x = Random::Range(-100.f, 100.f);
421     direction.y = Random::Range(-100.f, 100.f);
422
423     direction.Normalize();
424     direction *= 0.1f;
425
426     for(uint32_t j = 0; j < 360; j++)
427     {
428       sinCosVariation.x = sinf(j * Math::PI / 180.f) * direction.x;
429       sinCosVariation.y = cosf(j * Math::PI / 180.f) * direction.y;
430       float key         = j / 360.f;
431       keySinCosVariation.Add(key, sinCosVariation);
432     }
433
434     mPositionVarAnimation[i] = Animation::New(3.f);
435     mPositionVarAnimation[i].AnimateBetween(Property(mMetaballs[i].actor, mMetaballs[i].positionVarIndex), keySinCosVariation);
436     mPositionVarAnimation[i].SetLooping(true);
437     mPositionVarAnimation[i].Play();
438   }
439 }
440
441 void MetaballExplosionController::ResetMetaballs(bool resetAnims)
442 {
443   for(uint32_t i = 0; i < METABALL_NUMBER; i++)
444   {
445     if(mDispersionAnimation[i])
446     {
447       mDispersionAnimation[i].Clear();
448     }
449
450     mMetaballs[i].position = Vector2(0.0f, 0.0f);
451     mMetaballs[i].actor.SetProperty(mMetaballs[i].positionIndex, mMetaballs[i].position);
452   }
453   mTimerDispersion.Stop();
454   mDispersion = 0;
455
456   mCompositionActor.SetProperty(mPositionIndex, Vector2(0, 0));
457 }
458
459 void MetaballExplosionController::DisperseBallAnimation(uint32_t ball)
460 {
461   Vector2 position;
462   position.x = Random::Range(-1.5f, 1.5f);
463   position.y = Random::Range(-1.5f, 1.5f);
464
465   mDispersionAnimation[ball] = Animation::New(2.0f * mTimeMultiplier);
466   mDispersionAnimation[ball].AnimateTo(Property(mMetaballs[ball].actor, mMetaballs[ball].positionIndex), position);
467   mDispersionAnimation[ball].Play();
468
469   if(ball == METABALL_NUMBER - 1)
470   {
471     mDispersionAnimation[ball].FinishedSignal().Connect(this, &MetaballExplosionController::LaunchResetMetaballPosition);
472   }
473 }
474
475 void MetaballExplosionController::LaunchResetMetaballPosition(Animation& source)
476 {
477   for(uint32_t i = 0; i < METABALL_NUMBER; i++)
478   {
479     mDispersionAnimation[i] = Animation::New(1.5f + i * 0.25f * mTimeMultiplier);
480     mDispersionAnimation[i].AnimateTo(Property(mMetaballs[i].actor, mMetaballs[i].positionIndex), Vector2(0, 0));
481     mDispersionAnimation[i].Play();
482
483     if(i == METABALL_NUMBER - 1)
484     {
485       mDispersionAnimation[i].FinishedSignal().Connect(this, &MetaballExplosionController::EndDisperseAnimation);
486     }
487   }
488 }
489
490 void MetaballExplosionController::EndDisperseAnimation(Animation& source)
491 {
492   mCompositionActor.SetProperty(mPositionIndex, Vector2(0, 0));
493 }
494
495 bool MetaballExplosionController::OnTimerDispersionTick()
496 {
497   if(mDispersion < METABALL_NUMBER)
498   {
499     DisperseBallAnimation(mDispersion);
500     mDispersion++;
501   }
502   return true;
503 }
504
505 void MetaballExplosionController::SetPositionToMetaballs(const Vector2& metaballCenter)
506 {
507   //We set the position for the metaballs based on click position
508   for(uint32_t i = 0; i < METABALL_NUMBER; i++)
509   {
510     mMetaballs[i].position = metaballCenter;
511     mMetaballs[i].actor.SetProperty(mMetaballs[i].positionIndex, mMetaballs[i].position);
512   }
513
514   mCompositionActor.SetProperty(mPositionIndex, metaballCenter);
515 }
516
517 bool MetaballExplosionController::OnTouch(Actor actor, const TouchEvent& touch)
518 {
519   float aspectR = mScreenSize.y / mScreenSize.x;
520
521   switch(touch.GetState(0))
522   {
523     case PointState::DOWN:
524     {
525       ResetMetaballs(true);
526
527       const Vector2 screen         = touch.GetScreenPosition(0);
528       Vector2       metaballCenter = Vector2((screen.x / mScreenSize.x) - 0.5f, (aspectR * (mScreenSize.y - screen.y) / mScreenSize.y) - 0.5f) * 2.0f;
529       SetPositionToMetaballs(metaballCenter);
530
531       break;
532     }
533     case PointState::MOTION:
534     {
535       const Vector2 screen         = touch.GetScreenPosition(0);
536       Vector2       metaballCenter = Vector2((screen.x / mScreenSize.x) - 0.5f, (aspectR * (mScreenSize.y - screen.y) / mScreenSize.y) - 0.5f) * 2.0f;
537       SetPositionToMetaballs(metaballCenter);
538       break;
539     }
540     case PointState::UP:
541     case PointState::LEAVE:
542     case PointState::INTERRUPTED:
543     {
544       mTimerDispersion.Start();
545       break;
546     }
547     default:
548       break;
549   }
550   return true;
551 }
552
553 void MetaballExplosionController::OnKeyEvent(const KeyEvent& event)
554 {
555   if(event.GetState() == KeyEvent::DOWN)
556   {
557     if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
558     {
559       mApplication.Quit();
560     }
561   }
562 }
563
564 /**
565  * Main entry point
566  */
567 int32_t DALI_EXPORT_API main(int argc, char** argv)
568 {
569   Application application = Application::New(&argc, &argv);
570
571   MetaballExplosionController test(application);
572
573   application.MainLoop();
574
575   return 0;
576 }