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