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.
19 #include <cstdint> // uint32_t, uint16_t etc
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>
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
37 namespace // unnamed namespace for constants
40 const char* const BACKGROUND_IMAGE(DEMO_IMAGE_DIR "background-2.jpg");
42 // number of metaballs
43 constexpr uint32_t METABALL_NUMBER = 6;
46 * Metadata for each ball
56 Property::Index positionIndex;
57 Property::Index positionVarIndex;
60 } // unnamed namespace
63 * Demo using Metaballs
65 * When the metaball is clicked it explodes to smaller balls
67 class MetaballExplosionController : public ConnectionTracker
74 MetaballExplosionController(Application& application);
79 virtual ~MetaballExplosionController();
82 * Creates the metaballs and initializes the scene
84 void Create(Application& app);
87 * Touch event handler to center metaballs at touch position
88 * and start explosion animation on release
90 bool OnTouch(Actor actor, const TouchEvent& touch);
93 * Key event handler to quit application on escape or back key
95 void OnKeyEvent(const KeyEvent& event);
98 Application& mApplication;
101 Texture mBackgroundTexture;
102 FrameBuffer mMetaballFBO;
105 MetaballInfo mMetaballs[METABALL_NUMBER];
107 Property::Index mPositionIndex;
108 Actor mCompositionActor;
111 Vector2 mCurrentTouchPosition;
112 Vector2 mMetaballPosVariation;
113 Vector2 mMetaballPosVariationFrom;
114 Vector2 mMetaballPosVariationTo;
115 Vector2 mMetaballCenter;
118 Animation mPositionVarAnimation[METABALL_NUMBER];
120 uint32_t mDispersion;
121 Animation mDispersionAnimation[METABALL_NUMBER];
123 Timer mTimerDispersion;
125 float mTimeMultiplier;
127 // Private helper functions
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
133 Geometry CreateGeometry(bool aspectMappedTexture = true);
136 * Create a actors and renderers for the metaballs
138 void CreateMetaballActors();
141 * Create the render task and FBO to render the metaballs into a texture
143 void CreateMetaballImage();
146 * Create the the final composition
148 void CreateComposition();
151 * Function to create animations for the small variations of position inside the metaball
153 void CreateAnimations();
156 * Function to reset metaball state
158 void ResetMetaballs(bool resetAnims);
161 * Function to create disperse each of the ball that compose the metaball when exploding
163 void DisperseBallAnimation(uint32_t ball);
166 * Function to make metaballs come back to reset position
168 void LaunchResetMetaballPosition(Animation& source);
171 * Function to set things at the end of the animation
173 void EndDisperseAnimation(Animation& source);
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)
179 bool OnTimerDispersionTick();
182 * Function to set the actual position of the metaballs when the user clicks the screen
184 void SetPositionToMetaballs(const Vector2& metaballCenter);
191 MetaballExplosionController::MetaballExplosionController(Application& application)
192 : mApplication(application),
194 mBackgroundTexture(),
200 mCurrentTouchPosition(),
201 mMetaballPosVariation(),
202 mMetaballPosVariationFrom(),
203 mMetaballPosVariationTo(),
205 mPositionVarAnimation(),
207 mDispersionAnimation(),
209 mTimeMultiplier(1.0f)
211 // Connect to the Application's Init signal
212 mApplication.InitSignal().Connect(this, &MetaballExplosionController::Create);
215 MetaballExplosionController::~MetaballExplosionController()
217 // Nothing to do here;
220 void MetaballExplosionController::Create(Application& app)
222 Window window = app.GetWindow();
224 window.KeyEventSignal().Connect(this, &MetaballExplosionController::OnKeyEvent);
226 mScreenSize = window.GetSize();
228 mTimeMultiplier = 1.0f;
230 window.SetBackgroundColor(Color::BLACK);
232 // Load background texture
233 mBackgroundTexture = DemoHelper::LoadTexture(BACKGROUND_IMAGE);
235 srand(static_cast<uint32_t>(time(0)));
237 //Create internal data
238 CreateMetaballActors();
239 CreateMetaballImage();
245 mTimerDispersion = Timer::New(150);
246 mTimerDispersion.TickSignal().Connect(this, &MetaballExplosionController::OnTimerDispersionTick);
248 // Connect the callback to the touch signal on the mesh actor
249 window.GetRootLayer().TouchedSignal().Connect(this, &MetaballExplosionController::OnTouch);
252 Geometry MetaballExplosionController::CreateGeometry(bool aspectMappedTexture)
254 const float aspect = mScreenSize.y / mScreenSize.x;
256 // Create vertices and specify their color
257 const float xsize = mScreenSize.x * 0.5;
259 // Create the meshdata for the metaballs
260 struct VertexPosition
269 VertexPosition vertices[] =
271 {Vector2(-xsize, -xsize * aspect)},
272 {Vector2(xsize, -xsize * aspect)},
273 {Vector2(-xsize, xsize * aspect)},
274 {Vector2(xsize, xsize * aspect)}};
276 const float textureAspect = (aspectMappedTexture) ? aspect : 1.0f;
277 VertexTexture textures[] =
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)}};
284 uint32_t numberOfVertices = sizeof(vertices) / sizeof(VertexPosition);
287 Property::Map positionVertexFormat;
288 positionVertexFormat["aPosition"] = Property::VECTOR2;
289 VertexBuffer positionVertices = VertexBuffer::New(positionVertexFormat);
290 positionVertices.SetData(vertices, numberOfVertices);
293 Property::Map textureVertexFormat;
294 textureVertexFormat["aTexture"] = Property::VECTOR2;
295 VertexBuffer textureVertices = VertexBuffer::New(textureVertexFormat);
296 textureVertices.SetData(textures, numberOfVertices);
299 const uint16_t indices[] = {0, 3, 1, 0, 2, 3};
301 // Create the geometry object
302 Geometry texturedQuadGeometry = Geometry::New();
303 texturedQuadGeometry.AddVertexBuffer(positionVertices);
304 texturedQuadGeometry.AddVertexBuffer(textureVertices);
306 texturedQuadGeometry.SetIndexBuffer(&indices[0], sizeof(indices) / sizeof(indices[0]));
308 return texturedQuadGeometry;
311 void MetaballExplosionController::CreateMetaballActors()
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);
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);
325 //Initialization of each of the metaballs
326 for(uint32_t i = 0; i < METABALL_NUMBER; i++)
328 mMetaballs[i].position = Vector2(0.0f, 0.0f);
329 mMetaballs[i].radius = mMetaballs[i].initRadius = Random::Range(0.05f, 0.07f);
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);
337 mMetaballs[i].positionIndex = mMetaballs[i].actor.RegisterProperty("uPositionMetaball", mMetaballs[i].position);
339 mMetaballs[i].positionVarIndex = mMetaballs[i].actor.RegisterProperty("uPositionVar", Vector2(0.f, 0.f));
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);
347 mMetaballRoot = Actor::New();
348 mMetaballRoot.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
349 for(uint32_t i = 0; i < METABALL_NUMBER; i++)
351 mMetaballRoot.Add(mMetaballs[i].actor);
355 void MetaballExplosionController::CreateMetaballImage()
357 // Create an FBO and a render task to create to render the metaballs with a fragment shader
358 Window window = mApplication.GetWindow();
360 mMetaballFBO = FrameBuffer::New(mScreenSize.x, mScreenSize.y);
362 window.Add(mMetaballRoot);
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);
375 void MetaballExplosionController::CreateComposition()
378 Shader shader = Shader::New(SHADER_METABALL_VERT, SHADER_METABALL_REFRACTION_FRAG);
380 // Create new texture set
381 auto textureSet = TextureSet::New();
382 textureSet.SetTexture(0u, mBackgroundTexture);
383 textureSet.SetTexture(1u, mMetaballFBO.GetColorTexture());
386 Geometry metaballGeom = CreateGeometry(false);
388 Renderer mRenderer = Renderer::New(metaballGeom, shader);
389 mRenderer.SetTextures(textureSet);
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);
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);
403 SetPositionToMetaballs(metaballCenter);
405 mCompositionActor.SetProperty(Actor::Property::SIZE, Vector2(mScreenSize.x, mScreenSize.y));
407 Window window = mApplication.GetWindow();
408 window.Add(mCompositionActor);
411 void MetaballExplosionController::CreateAnimations()
415 for(uint32_t i = 0; i < METABALL_NUMBER; i++)
417 KeyFrames keySinCosVariation = KeyFrames::New();
418 Vector2 sinCosVariation(0, 0);
420 direction.x = Random::Range(-100.f, 100.f);
421 direction.y = Random::Range(-100.f, 100.f);
423 direction.Normalize();
426 for(uint32_t j = 0; j < 360; j++)
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);
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();
441 void MetaballExplosionController::ResetMetaballs(bool resetAnims)
443 for(uint32_t i = 0; i < METABALL_NUMBER; i++)
445 if(mDispersionAnimation[i])
447 mDispersionAnimation[i].Clear();
450 mMetaballs[i].position = Vector2(0.0f, 0.0f);
451 mMetaballs[i].actor.SetProperty(mMetaballs[i].positionIndex, mMetaballs[i].position);
453 mTimerDispersion.Stop();
456 mCompositionActor.SetProperty(mPositionIndex, Vector2(0, 0));
459 void MetaballExplosionController::DisperseBallAnimation(uint32_t ball)
462 position.x = Random::Range(-1.5f, 1.5f);
463 position.y = Random::Range(-1.5f, 1.5f);
465 mDispersionAnimation[ball] = Animation::New(2.0f * mTimeMultiplier);
466 mDispersionAnimation[ball].AnimateTo(Property(mMetaballs[ball].actor, mMetaballs[ball].positionIndex), position);
467 mDispersionAnimation[ball].Play();
469 if(ball == METABALL_NUMBER - 1)
471 mDispersionAnimation[ball].FinishedSignal().Connect(this, &MetaballExplosionController::LaunchResetMetaballPosition);
475 void MetaballExplosionController::LaunchResetMetaballPosition(Animation& source)
477 for(uint32_t i = 0; i < METABALL_NUMBER; i++)
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();
483 if(i == METABALL_NUMBER - 1)
485 mDispersionAnimation[i].FinishedSignal().Connect(this, &MetaballExplosionController::EndDisperseAnimation);
490 void MetaballExplosionController::EndDisperseAnimation(Animation& source)
492 mCompositionActor.SetProperty(mPositionIndex, Vector2(0, 0));
495 bool MetaballExplosionController::OnTimerDispersionTick()
497 if(mDispersion < METABALL_NUMBER)
499 DisperseBallAnimation(mDispersion);
505 void MetaballExplosionController::SetPositionToMetaballs(const Vector2& metaballCenter)
507 //We set the position for the metaballs based on click position
508 for(uint32_t i = 0; i < METABALL_NUMBER; i++)
510 mMetaballs[i].position = metaballCenter;
511 mMetaballs[i].actor.SetProperty(mMetaballs[i].positionIndex, mMetaballs[i].position);
514 mCompositionActor.SetProperty(mPositionIndex, metaballCenter);
517 bool MetaballExplosionController::OnTouch(Actor actor, const TouchEvent& touch)
519 float aspectR = mScreenSize.y / mScreenSize.x;
521 switch(touch.GetState(0))
523 case PointState::DOWN:
525 ResetMetaballs(true);
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);
533 case PointState::MOTION:
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);
541 case PointState::LEAVE:
542 case PointState::INTERRUPTED:
544 mTimerDispersion.Start();
553 void MetaballExplosionController::OnKeyEvent(const KeyEvent& event)
555 if(event.GetState() == KeyEvent::DOWN)
557 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
567 int32_t DALI_EXPORT_API main(int argc, char** argv)
569 Application application = Application::New(&argc, &argv);
571 MetaballExplosionController test(application);
573 application.MainLoop();