Merge "Fixing bug in pbr demo ktx loader" into devel/graphics
[platform/core/uifw/dali-demo.git] / examples / particles / particles-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 // INTERNAL INCLUDES
19 #include <fstream>
20 #include <iostream>
21 #include <list>
22 #include <memory>
23 #include <numeric>
24 #include <random>
25 #include "dali/devel-api/adaptor-framework/tilt-sensor.h"
26 #include "dali/public-api/actors/camera-actor.h"
27 #include "dali/public-api/actors/layer.h"
28 #include "dali/public-api/adaptor-framework/application.h"
29 #include "dali/public-api/adaptor-framework/key.h"
30 #include "dali/public-api/events/pan-gesture-detector.h"
31 #include "dali/public-api/events/tap-gesture-detector.h"
32 #include "dali/public-api/events/touch-event.h"
33 #include "dali/public-api/object/property-index-ranges.h"
34 #include "dali/public-api/render-tasks/render-task-list.h"
35 #include "dali/public-api/render-tasks/render-task.h"
36 #include "float-rand.h"
37 #include "particle-field.h"
38 #include "particle-view.h"
39 #include "utils.h"
40
41 using namespace Dali;
42
43 namespace
44 {
45 const float PARTICLE_ALPHA       = .75;
46 const float ALPHA_TEST_REF_VALUE = .0625;
47
48 const float NEAR_FADE = 0.04; // normalized depth
49 const float FAR_FADE  = 0.8;  // normalized depth
50
51 const ParticleField PARTICLE_FIELD = {
52   200.f,                        // particle size
53   Vector3(2500., 2500., 7800.), // box size - depth needs to be >= camera depth range
54   Vector3(6., 6., 12.),         // number of particles
55   .333,                         // size variance
56   1.,                           // noise amount
57   200.,                         // disperse
58   250.,                         // motion scale
59   15.,                          // motion cycle length
60   6.,                           // twinkle frequency
61   .11,                          // twinkle size scale
62   .333                          // twinkle opacity weight
63 };
64
65 const float WORLD_ANGULAR_VELOCITY = .08;  // radians per seconds
66 const float WORLD_LINEAR_VELOCITY  = -500; // units along z
67
68 const float SCATTER_RADIUS       = 450.0f;
69 const float SCATTER_AMOUNT       = 180.0f;
70 const float SCATTER_DURATION_OUT = .8f;
71 const float SCATTER_DURATION_IN  = 1.5f;
72
73 const float FADE_DURATION            = 1.2f;
74 const float FADEOUT_SPEED_MULTIPLIER = 4.f; // speed multiplier upon fading out.
75
76 const float FOCAL_LENGTH = 0.5f; // normalized depth value where particles appear sharp.
77 const float APERTURE     = 2.2f; // distance scale - the higher it is, the quicker the particles get blurred out moving away from the focal length.
78
79 const ColorRange DEFAULT_COLOR_RANGE{Vector3(0., 48. / 255., 1.), Vector3(0., 216. / 255., 1.)};
80
81 const float TILT_SCALE         = 0.2;
82 const float TILT_RANGE_DEGREES = 30.f;
83
84 FloatRand sFloatRand;
85
86 class TiltFilter
87 {
88 public:
89   void Reset()
90   {
91     std::fill(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f));
92   }
93
94   void Add(Dali::Vector2 tilt)
95   {
96     mTiltSamples[mIdxNextSample] = tilt;
97     mIdxNextSample               = (mIdxNextSample + 1) % FILTER_SIZE;
98   }
99
100   Dali::Vector2 Filter() const
101   {
102     return std::accumulate(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f)) / FILTER_SIZE;
103   }
104
105 private:
106   enum
107   {
108     FILTER_SIZE = 8u
109   };
110
111   Dali::Vector2 mTiltSamples[FILTER_SIZE];
112   size_t        mIdxNextSample = 0;
113 };
114
115 class ParticlesExample : public ConnectionTracker
116 {
117 public:
118   ParticlesExample(Application& app)
119   : mApp(app)
120   {
121     mApp.InitSignal().Connect(this, &ParticlesExample::OnInit);
122     mApp.TerminateSignal().Connect(this, &ParticlesExample::OnTerminate);
123   }
124
125   ~ParticlesExample() = default;
126
127 private:
128   Application& mApp;
129
130   CameraActor mCamera;
131
132   Actor      mWorld;
133   Vector2    mAngularPosition;
134   ColorRange mColors;
135
136   std::unique_ptr<ParticleView> mParticles;
137   std::unique_ptr<ParticleView> mExpiringParticles;
138
139   TapGestureDetector mDoubleTapGesture;
140
141   TiltSensor mTiltSensor;
142   TiltFilter mTiltFilter;
143
144   PanGestureDetector mPanGesture;
145
146   void OnInit(Application& application)
147   {
148     Window window    = application.GetWindow();
149     auto   rootLayer = window.GetRootLayer();
150     rootLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::Behavior::LAYER_3D);
151
152     window.KeyEventSignal().Connect(this, &ParticlesExample::OnKeyEvent);
153     window.GetRootLayer().TouchedSignal().Connect(this, &ParticlesExample::OnTouched);
154
155     auto tiltSensor = TiltSensor::Get();
156     if(tiltSensor.Start())
157     {
158       // Get notifications when the device is tilted
159       tiltSensor.TiltedSignal().Connect(this, &ParticlesExample::OnTilted);
160     }
161     else
162     {
163       mPanGesture = PanGestureDetector::New();
164       mPanGesture.Attach(rootLayer);
165       mPanGesture.DetectedSignal().Connect(this, &ParticlesExample::OnPan);
166     }
167
168     // Get camera
169     RenderTaskList tasks    = window.GetRenderTaskList();
170     RenderTask     mainPass = tasks.GetTask(0);
171     CameraActor    camera   = mainPass.GetCameraActor();
172     mCamera                 = camera;
173
174     // Create world - particles and clock are added to it; this is what we apply tilt to.
175     auto world = CreateActor();
176     world.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
177     window.Add(world);
178     mWorld = world;
179
180     // Create particles
181     TriggerColorTransition(DEFAULT_COLOR_RANGE);
182
183     // Setup double tap detector for color change
184     mDoubleTapGesture = TapGestureDetector::New(2);
185     mDoubleTapGesture.Attach(rootLayer);
186     mDoubleTapGesture.DetectedSignal().Connect(this, &ParticlesExample::OnDoubleTap);
187   }
188
189   void OnTerminate(Application& app)
190   {
191     UnparentAndReset(mWorld);
192
193     mDoubleTapGesture.Reset();
194     mPanGesture.Reset();
195     mTiltSensor.Reset();
196   }
197
198   void OnPause(Application& app)
199   {
200     mTiltSensor.Stop();
201   }
202
203   void OnResume(Application& app)
204   {
205     mTiltSensor.Start();
206   }
207
208   void OnKeyEvent(const KeyEvent& event)
209   {
210     if(event.GetState() == KeyEvent::UP) // single keystrokes
211     {
212       if(IsKey(event, DALI_KEY_ESCAPE) || IsKey(event, DALI_KEY_BACK))
213       {
214         mApp.Quit();
215       }
216     }
217   }
218
219   bool OnTouched(Actor a, const TouchEvent& event)
220   {
221     if(event.GetPointCount() > 0)
222     {
223       auto screenPos = event.GetScreenPosition(0);
224       switch(event.GetState(0))
225       {
226         case PointState::STARTED:
227         {
228           mParticles->Scatter(SCATTER_RADIUS, SCATTER_AMOUNT, SCATTER_DURATION_OUT, SCATTER_DURATION_IN);
229
230           Vector3 ray = GetViewRay(screenPos);
231           mParticles->SetScatterRay(ray);
232         }
233         break;
234
235         default:
236           break;
237       }
238     }
239
240     return false;
241   }
242
243   void OnDoubleTap(Actor /*actor*/, const TapGesture& /*gesture*/)
244   {
245     if(!mExpiringParticles)
246     {
247       mColors.rgb0 = Vector3::ONE - mColors.rgb1;
248       mColors.rgb1 = FromHueSaturationLightness(Vector3(sFloatRand() * 360.f, sFloatRand() * .5f + .5f, sFloatRand() * .25 + .75f));
249
250       TriggerColorTransition(mColors);
251     }
252   }
253
254   void OnPan(Actor actor, const PanGesture& gesture)
255   {
256     auto       tilt = gesture.GetDisplacement() / Vector2(mApp.GetWindow().GetSize()) * TILT_SCALE;
257     Quaternion q(Radian(-tilt.y), Radian(tilt.x), Radian(0.f));
258     Quaternion q0 = mWorld.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();
259     mWorld.SetProperty(Actor::Property::ORIENTATION, q * q0);
260   }
261
262   void OnTilted(const TiltSensor& sensor)
263   {
264     mTiltFilter.Add(Vector2(sensor.GetPitch(), sensor.GetRoll()));
265     Vector2    tilt = mTiltFilter.Filter() * TILT_RANGE_DEGREES;
266     Quaternion q(Radian(Degree(tilt.x)), Radian(Degree(tilt.y)), Radian(0.f));
267     mWorld.SetProperty(Actor::Property::ORIENTATION, q);
268   }
269
270   Vector3 GetViewRay(const Vector2& screenPos)
271   {
272     Vector2 screenSize    = mApp.GetWindow().GetSize();
273     Vector2 normScreenPos = (screenPos / screenSize) * 2.f - Vector2::ONE;
274
275     const float fov    = mCamera.GetProperty(CameraActor::Property::FIELD_OF_VIEW).Get<float>();
276     const float tanFov = std::tan(fov);
277
278     const float zNear = mCamera.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get<float>();
279     const float hProj = zNear * tanFov;
280
281     const float aspectRatio = mCamera.GetProperty(CameraActor::Property::ASPECT_RATIO).Get<float>();
282     const float wProj       = hProj * aspectRatio;
283
284     // Get camera orientation for view space ray casting. Assume:
285     // - this to be world space, i.e. no parent transforms;
286     // - no scaling;
287     Quaternion cameraOrientation = mCamera.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();
288
289     Matrix worldCamera;
290     worldCamera.SetTransformComponents(Vector3::ONE, cameraOrientation, Vector3::ZERO);
291
292     float*  data = worldCamera.AsFloat();
293     Vector3 xWorldCamera(data[0], data[4], data[8]);
294     xWorldCamera *= wProj * normScreenPos.x / xWorldCamera.Length();
295
296     Vector3 yWorldCamera(data[1], data[5], data[9]);
297     yWorldCamera *= hProj * normScreenPos.y / yWorldCamera.Length();
298
299     Vector3 zWorldCamera(data[2], data[6], data[10]);
300     Vector3 ray = xWorldCamera + yWorldCamera + zWorldCamera * -zNear;
301     ray.Normalize();
302
303     return ray;
304   }
305
306   void TriggerColorTransition(const ColorRange& range)
307   {
308     if(mParticles)
309     {
310       mExpiringParticles = std::move(mParticles);
311
312       // NOTE: this will break the perfect looping, but we can get away with it because we're fading out.
313       mExpiringParticles->SetLinearVelocity(WORLD_LINEAR_VELOCITY * FADEOUT_SPEED_MULTIPLIER);
314
315       auto& p = mExpiringParticles;
316       mExpiringParticles->Fade(FADE_DURATION, 0.f, AlphaFunction::EASE_IN, [&p](Animation&) {
317         p.reset();
318       });
319     }
320
321     mParticles.reset(new ParticleView(PARTICLE_FIELD, mWorld, mCamera));
322     mParticles->SetColorRange(range);
323     mParticles->SetFocalLength(FOCAL_LENGTH);
324     mParticles->SetAperture(APERTURE);
325     mParticles->SetAlphaTestRefValue(ALPHA_TEST_REF_VALUE);
326     mParticles->SetFadeRange(NEAR_FADE, FAR_FADE);
327     mParticles->SetAngularVelocity(WORLD_ANGULAR_VELOCITY);
328     mParticles->SetLinearVelocity(WORLD_LINEAR_VELOCITY);
329     mParticles->Fade(FADE_DURATION, PARTICLE_ALPHA, 0.f, AlphaFunction::EASE_OUT);
330   }
331 };
332
333 } // namespace
334
335 int DALI_EXPORT_API main(int argc, char** argv)
336 {
337   Application      application = Application::New(&argc, &argv, DEMO_THEME_PATH);
338   ParticlesExample example(application);
339   application.MainLoop();
340   return 0;
341 }