Added Waves example.
[platform/core/uifw/dali-demo.git] / examples / waves / waves-example.cpp
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (   "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 "utils.h"
20 #include "dali/devel-api/adaptor-framework/tilt-sensor.h"
21 #include "dali/public-api/adaptor-framework/application.h"
22 #include "dali/public-api/adaptor-framework/key.h"
23 #include "dali/public-api/animation/animation.h"
24 #include "dali/public-api/events/pan-gesture-detector.h"
25 #include "dali/public-api/events/tap-gesture-detector.h"
26 #include "dali/public-api/events/key-event.h"
27 #include "dali/public-api/actors/camera-actor.h"
28 #include "dali/public-api/actors/layer.h"
29 #include "dali/public-api/render-tasks/render-task.h"
30 #include "dali/public-api/render-tasks/render-task-list.h"
31 #include <fstream>
32 #include <iostream>
33 #include <numeric>
34
35 using namespace Dali;
36
37 namespace
38 {
39
40 constexpr std::string_view WAVES_VSH =
41   "#define FMA(a, b, c) ((a) * (b) + (c))\n"  // fused multiply-add
42 DALI_COMPOSE_SHADER(
43   precision highp float;
44
45   const float kTile = 1.;
46
47   const float kPi = 3.1415926535;
48   const float kEpsilon = 1. / 32.;
49
50   // DALI uniforms
51   uniform vec3 uSize;
52   uniform mat4 uModelView;
53   uniform mat4 uProjection;
54   uniform mat3 uNormalMatrix;
55
56   // our uniforms
57   uniform float uTime;
58   uniform vec2 uScrollScale;
59   uniform float uWaveRate;
60   uniform float uWaveAmplitude;
61   uniform float uParallaxAmount;
62
63   attribute vec2 aPosition;
64   attribute vec2 aTexCoord;
65
66   varying vec2 vUv;
67   varying vec3 vViewPos;
68   varying vec3 vNormal;
69   varying float vHeight;
70
71   float CubicHermite(float B, float C, float t)
72   {
73     float dCB = (C - B) * .5;
74     float A = B - dCB;
75     float D = B + dCB;
76     vec3 p = vec3(D + .5 * (((B - C) * 3.) - A), A - 2.5 * B + 2. * C - D,
77       .5 * (C - A));
78     return FMA(FMA(FMA(p.x, t, p.y), t, p.z), t, B);
79   }
80
81   float Hash(float n)
82   {
83     return fract(sin(n) * 43751.5453123);
84   }
85
86   float HeightAtTile(vec2 pos)
87   {
88     float rate = Hash(Hash(pos.x) * Hash(pos.y));
89
90     return (sin(uTime * rate * uWaveRate) * .5 + .5) * uWaveAmplitude;
91  }
92
93   float CalculateHeight(vec2 position)
94   {
95     vec2 tile = floor(position);
96     position = fract(position);
97
98     vec2 cp = vec2(
99       CubicHermite(
100         HeightAtTile(tile + vec2( kTile * -0.5, kTile * -0.5)),
101         HeightAtTile(tile + vec2( kTile * +0.5, kTile * -0.5)),
102         position.x),
103       CubicHermite(
104         HeightAtTile(tile + vec2( kTile * -0.5, kTile * +0.5)),
105         HeightAtTile(tile + vec2( kTile * +0.5, kTile * +0.5)),
106         position.x)
107     );
108
109     return CubicHermite(cp.x, cp.y, position.y);
110   }
111
112   vec3 CalculateNormal(vec2 position)
113   {
114     vec3 normal = vec3(
115       CalculateHeight(vec2(position.x - kEpsilon, position.y)) -
116         CalculateHeight(vec2(position.x + kEpsilon, position.y)),
117       .25,
118       CalculateHeight(vec2(position.x, position.y - kEpsilon)) -
119         CalculateHeight(vec2(position.x, position.y + kEpsilon))
120     );
121     return normal;
122   }
123
124   void main()
125   {
126     vUv = aTexCoord;
127
128     vec2 scrollPosition = aPosition * uScrollScale + vec2(0., uTime * -kPi);
129     vNormal = uNormalMatrix * CalculateNormal(scrollPosition);
130
131     float h = CalculateHeight(scrollPosition);
132     vHeight = h * uParallaxAmount;
133     vec3 position = vec3(aPosition.x, h, aPosition.y);
134
135     vec4 viewPosition = uModelView * vec4(position * uSize, 1.);
136     vViewPos = -viewPosition.xyz;
137
138     gl_Position = uProjection * viewPosition;
139   });
140
141 constexpr std::string_view WAVES_FSH = DALI_COMPOSE_SHADER(
142   precision highp float;
143
144   uniform vec4 uColor; // DALi
145   uniform sampler2D uNormalMap; // DALi
146
147   uniform vec3 uInvLightDir;
148   uniform vec3 uLightColorSqr;
149   uniform vec3 uAmbientColor;
150
151   uniform float uNormalMapWeight;
152   uniform float uSpecularity;
153
154   varying vec2 vUv;
155   varying vec3 vNormal;
156   varying vec3 vViewPos;
157   varying float vHeight;
158
159   float Rand(vec2 co)
160   {
161     return fract(sin(dot(co.xy, vec2(12.98981, 78.2331))) * 43758.5453);
162   }
163
164   float Sum(vec3 v)
165   {
166     return v.x + v.y + v.z;
167   }
168
169   void main()
170   {
171     vec3 viewPos = normalize(vViewPos);
172     vec2 uv2 = vUv + vViewPos.xy / vViewPos.z * vHeight + vec2(.5, 0.);
173
174     vec3 perturbNormal = texture2D(uNormalMap, vUv).rgb * 2. - 1.;
175     vec3 perturbNormal2 = texture2D(uNormalMap, uv2).rgb * 2. - 1.;
176     vec3 normal = normalize(vNormal + perturbNormal * uNormalMapWeight);
177     vec3 normal2 = normalize(vNormal + perturbNormal2 * uNormalMapWeight);
178
179     vec3 color = uAmbientColor;
180     float d = max(0., dot(normal, -uInvLightDir));
181     color += uColor.rgb * d;
182
183     vec3 reflected = reflect(uInvLightDir, normal);
184     d = max(0., dot(reflected, viewPos));
185     color += pow(d, uSpecularity) * uLightColorSqr;
186
187     reflected = reflect(uInvLightDir, normal2);
188     d = max(0., dot(reflected, viewPos));
189     color += pow(d, uSpecularity) * uLightColorSqr;
190
191     gl_FragColor = vec4(color, 1.);
192   });
193
194 const float TIME_STEP = 0.0952664626;
195
196 const std::string UNIFORM_LIGHT_COLOR_SQR = "uLightColorSqr";
197 const std::string UNIFORM_AMBIENT_COLOR = "uAmbientColor";
198 const std::string UNIFORM_INV_LIGHT_DIR = "uInvLightDir";
199 const std::string UNIFORM_SCROLL_SCALE = "uScrollScale";
200 const std::string UNIFORM_WAVE_RATE = "uWaveRate";
201 const std::string UNIFORM_WAVE_AMPLITUDE = "uWaveAmplitude";
202 const std::string UNIFORM_NORMAL_MAP_WEIGHT = "uNormalMapWeight";
203 const std::string UNIFORM_SPECULARITY = "uSpecularity";
204 const std::string UNIFORM_PARALLAX_AMOUNT = "uParallaxAmount";
205 const std::string UNIFORM_TIME = "uTime";
206
207 const Vector3 WAVES_COLOR { .78f, .64f, .26f };
208 const Vector3 LIGHT_COLOR { 1.0f, 0.91f, 0.6f };
209 const Vector3 AMBIENT_COLOR { .002f, .001f, .001f };
210
211 const Vector3 INV_LIGHT_DIR = Normalized(Vector3{ .125f, .8f, -.55f });
212
213 const Vector2 SCROLL_SCALE{ 1.f, 3.5f };
214 const float WAVE_RATE = 12.17f;
215 const float WAVE_AMPLITUDE = 1.f;
216 const float NORMAL_MAP_WEIGHT = 0.05f;
217 const float SPECULARITY = 512.f;
218 const float PARALLAX_AMOUNT = .25f;
219
220 const float TILT_RANGE_DEGREES = 30.f;
221
222 const float TRANSITION_DURATION = 1.2f;
223 const float TRANSITION_TIME_SCALE = 6.f;
224
225 const std::string_view NORMAL_MAP_NAME = "noise512.png";
226
227 Vector3 RandomColor()
228 {
229   float r = .5f + (rand() % RAND_MAX) / float(RAND_MAX) * .5f;
230   float g = .5f + (rand() % RAND_MAX) / float(RAND_MAX) * .5f;
231   float b = .5f + (rand() % RAND_MAX) / float(RAND_MAX) * .5f;
232   return Vector3(r, g, b);
233 }
234
235 class TiltFilter
236 {
237 public:
238   void Reset()
239   {
240     std::fill(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f));
241   }
242
243   void Add(Dali::Vector2 tilt)
244   {
245     mTiltSamples[mIdxNextSample] = tilt;
246     mIdxNextSample = (mIdxNextSample + 1) % FILTER_SIZE;
247   }
248
249   Dali::Vector2 Filter() const
250   {
251     return std::accumulate(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f)) / FILTER_SIZE;
252   }
253
254 private:
255   enum { FILTER_SIZE = 8u };
256
257   Dali::Vector2 mTiltSamples[FILTER_SIZE];
258   size_t mIdxNextSample = 0;
259 };
260
261 } // nonamespace
262
263 class WavesExample : public ConnectionTracker
264 {
265 public:
266   WavesExample( Application& app )
267   : mApp( app )
268   {
269     mApp.InitSignal().Connect( this, &WavesExample::Create );
270     mApp.TerminateSignal().Connect( this, &WavesExample::Destroy );
271   }
272
273   ~WavesExample() = default;
274
275 private:
276   Application& mApp;
277
278   CameraActor mCamera;  // no ownership
279
280   Actor mWaves;
281   Shader mWaveShader;
282
283   Property::Index mUInvLightDir;
284   Property::Index mULightColorSqr;
285   Property::Index mUAmbientColor;
286   Property::Index mUWaveRate;
287   Property::Index mUWaveAmplitude;
288   Property::Index mUScrollScale;
289   Property::Index mUNormalMapWeight;
290   Property::Index mUSpecularity;
291   Property::Index mUParallaxAmount;
292   Property::Index mUTime;
293
294   TapGestureDetector mDoubleTapGesture;
295
296   TiltSensor mTiltSensor;
297   TiltFilter mTiltFilter;
298
299   PanGestureDetector mPanGesture;
300
301   Animation mTimeAnim;
302   Animation mTransitionAnim;
303
304   void Create( Application& application )
305   {
306     Window window = application.GetWindow();
307     auto rootLayer = window.GetRootLayer();
308
309     window.SetBackgroundColor(Vector4(WAVES_COLOR * .5f));
310
311     // Get camera
312     RenderTaskList tasks = window.GetRenderTaskList();
313     RenderTask mainPass = tasks.GetTask(0);
314     CameraActor camera = mainPass.GetCameraActor();
315     mCamera = camera;
316
317     // NOTE: watchface doesn't tolerate modification of the camera well;
318     /// we're better off rotating the world.
319     Quaternion baseOrientation (Radian(Degree(-150.f)), Radian(M_PI), Radian(0.f));
320
321     auto shader = CreateShader();
322
323     // Create geometry
324     Geometry geom = CreateTesselatedQuad(16, 64, Vector2{ .25f, 3.8f }, [](const Vector2& v) {
325       float y = v.y + .5f;  // 0..1
326       y = std::sqrt(y) - .5f; // perspective correction - increase vertex density closer to viewer
327
328       float x = v.x + v.x * (1.f - y) * 5.5f;
329
330       y -= .24f;  // further translation
331       return Vector2{ x, y };
332     }, [](const Vector2& v) {
333       return Vector2{ v.x, std::sqrt(v.y) };
334     });
335
336     // Create texture
337     auto normalMap = LoadTexture(std::string(DEMO_IMAGE_DIR) + NORMAL_MAP_NAME.data());
338
339     TextureSet textures = TextureSet::New();
340     textures.SetTexture(0, normalMap);
341
342     Sampler sampler = Sampler::New();
343     sampler.SetFilterMode(FilterMode::NEAREST, FilterMode::NEAREST);
344     sampler.SetWrapMode(WrapMode::REPEAT, WrapMode::REPEAT);
345     textures.SetSampler(0, sampler);
346
347     // Create renderer
348     Renderer renderer = CreateRenderer(textures, geom, shader, OPTION_DEPTH_TEST | OPTION_DEPTH_WRITE);
349
350     auto waves = CreateActor();
351     auto size = Vector2(window.GetSize());
352     waves.SetProperty(Actor::Property::SIZE, Vector3(size.x, 100.f, size.y));
353     waves.SetProperty(Actor::Property::ORIENTATION, baseOrientation);
354     waves.SetProperty(Actor::Property::COLOR, WAVES_COLOR);
355     waves.AddRenderer(renderer);
356
357     window.Add(waves);
358     mWaves = waves;
359
360     window.KeyEventSignal().Connect( this, &WavesExample::OnKeyEvent );
361
362     // Setup double tap detector for color change
363     mDoubleTapGesture = TapGestureDetector::New(2);
364     mDoubleTapGesture.Attach(rootLayer);
365     mDoubleTapGesture.DetectedSignal().Connect(this, &WavesExample::OnDoubleTap);
366
367     // Touch controls
368     mTiltSensor = TiltSensor::Get();
369     if ( mTiltSensor.Start() )
370     {
371       // Get notifications when the device is tilted
372       mTiltSensor.TiltedSignal().Connect( this, &WavesExample::OnTilted );
373     }
374     else
375     {
376       mPanGesture = PanGestureDetector::New();
377       mPanGesture.Attach(rootLayer);
378       mPanGesture.DetectedSignal().Connect(this, &WavesExample::OnPan);
379     }
380
381     // Register for suspend / resume
382     application.PauseSignal().Connect(this, &WavesExample::OnPause);
383     application.ResumeSignal().Connect(this, &WavesExample::OnResume);
384
385     // Create animation for the simulation of time
386     Animation animTime = Animation::New(1.f);
387     animTime.AnimateBy(Property(mWaveShader, mUTime), TIME_STEP);
388     animTime.FinishedSignal().Connect(this, &WavesExample::OnTimeAnimFinished);
389     animTime.Play();
390     mTimeAnim = animTime;
391   }
392
393   void Destroy( Application& app)
394   {
395     mCamera.Reset();
396
397     mDoubleTapGesture.Reset();
398     mPanGesture.Reset();
399
400     UnparentAndReset(mWaves);
401   }
402
403   Shader CreateShader()
404   {
405     Vector3 lightColorSqr{ LIGHT_COLOR };
406     Vector3 ambientColor = AMBIENT_COLOR;
407     Vector3 invLightDir = INV_LIGHT_DIR;
408     Vector2 scrollScale = SCROLL_SCALE;
409     float waveRate = WAVE_RATE;
410     float waveAmp = WAVE_AMPLITUDE;
411     float normalMapWeight = NORMAL_MAP_WEIGHT;
412     float specularity = SPECULARITY;
413     float parallaxAmount = PARALLAX_AMOUNT;
414     if (mWaveShader)
415     {
416       lightColorSqr = mWaveShader.GetProperty(mULightColorSqr).Get<Vector3>();
417       ambientColor = mWaveShader.GetProperty(mUAmbientColor).Get<Vector3>();
418       invLightDir = mWaveShader.GetProperty(mUInvLightDir).Get<Vector3>();
419       scrollScale = mWaveShader.GetProperty(mUScrollScale).Get<Vector2>();
420       waveRate = mWaveShader.GetProperty(mUWaveRate).Get<float>();
421       waveAmp = mWaveShader.GetProperty(mUWaveAmplitude).Get<float>();
422       normalMapWeight = mWaveShader.GetProperty(mUNormalMapWeight).Get<float>();
423       specularity = mWaveShader.GetProperty(mUSpecularity).Get<float>();
424     }
425
426     Shader shader = Shader::New(WAVES_VSH.data(), WAVES_FSH.data(), Shader::Hint::MODIFIES_GEOMETRY);
427     mULightColorSqr = shader.RegisterProperty(UNIFORM_LIGHT_COLOR_SQR, lightColorSqr);
428     mUAmbientColor = shader.RegisterProperty(UNIFORM_AMBIENT_COLOR, ambientColor);
429     mUInvLightDir = shader.RegisterProperty(UNIFORM_INV_LIGHT_DIR, invLightDir);
430     mUScrollScale = shader.RegisterProperty(UNIFORM_SCROLL_SCALE, scrollScale);
431     mUWaveRate = shader.RegisterProperty(UNIFORM_WAVE_RATE, waveRate);
432     mUWaveAmplitude = shader.RegisterProperty(UNIFORM_WAVE_AMPLITUDE, waveAmp);
433     mUNormalMapWeight = shader.RegisterProperty(UNIFORM_NORMAL_MAP_WEIGHT, normalMapWeight);
434     mUSpecularity = shader.RegisterProperty(UNIFORM_SPECULARITY, specularity);
435     mUParallaxAmount = shader.RegisterProperty(UNIFORM_PARALLAX_AMOUNT, parallaxAmount);
436     mUTime = shader.RegisterProperty(UNIFORM_TIME, 0.f);
437
438     auto window = mApp.GetWindow();
439     shader.RegisterProperty("uScreenHalfSize", Vector2(window.GetSize()) * .5f);
440     mWaveShader = shader;
441
442     return shader;
443   }
444
445   void TriggerColorTransition(Vector3 wavesColor, Vector3 lightColor)
446   {
447     if (mTransitionAnim)
448     {
449       mTransitionAnim.Stop();
450     }
451
452     mTimeAnim.FinishedSignal().Disconnect(this, &WavesExample::OnTimeAnimFinished);
453     mTimeAnim.Stop();
454
455     Animation anim = Animation::New(TRANSITION_DURATION);
456     anim.AnimateTo(Property(mWaves, Actor::Property::COLOR), Vector4(wavesColor), AlphaFunction::EASE_IN_OUT);
457     anim.AnimateTo(Property(mWaveShader, mULightColorSqr), lightColor * lightColor, AlphaFunction::EASE_IN_OUT);
458     anim.AnimateBy(Property(mWaveShader, mUTime), TRANSITION_DURATION * TIME_STEP * TRANSITION_TIME_SCALE, AlphaFunction::EASE_IN_OUT);
459     anim.FinishedSignal().Connect(this, &WavesExample::OnTransitionFinished);
460     anim.Play();
461     mTransitionAnim = anim;
462   }
463
464   void OnTimeAnimFinished(Animation& anim)
465   {
466     anim.Play();
467   }
468
469   void OnTransitionFinished(Animation& anim)
470   {
471     mTransitionAnim.Reset();
472     mTimeAnim.FinishedSignal().Connect(this, &WavesExample::OnTimeAnimFinished);
473     mTimeAnim.Play();
474   }
475
476   void OnPause(Application& app)
477   {
478     mTimeAnim.Pause();
479     mTiltSensor.Stop();
480   }
481
482   void OnResume(Application& app)
483   {
484     mTiltSensor.Start();
485     mTimeAnim.Play();
486   }
487
488   void OnKeyEvent(const KeyEvent& event)
489   {
490     if ( event.GetState() == KeyEvent::UP)  // single keystrokes
491     {
492       if( IsKey( event, DALI_KEY_ESCAPE ) || IsKey( event, DALI_KEY_BACK ) )
493       {
494         mApp.Quit();
495       }
496     }
497   }
498
499   void OnDoubleTap(Actor /*actor*/, const TapGesture& gesture)
500   {
501     Vector3 lightColor = mWaveShader.GetProperty(mULightColorSqr).Get<Vector3>();
502     TriggerColorTransition(lightColor, RandomColor());
503   }
504
505   void OnPan(Actor actor, const PanGesture& gesture)
506   {
507     auto tilt = gesture.GetDisplacement() / Vector2(mApp.GetWindow().GetSize());
508     switch (gesture.GetState())
509     {
510     case GestureState::STARTED:
511       mTiltFilter.Add(tilt);
512       break;
513
514     case GestureState::CONTINUING:
515       mTiltFilter.Add(mTiltFilter.Filter() + tilt);
516       break;
517
518     default:
519       break;
520     }
521
522     UpdateLightDirection();
523   }
524
525   void OnTilted( const TiltSensor& sensor)
526   {
527     mTiltFilter.Add(Vector2(sensor.GetPitch(), sensor.GetRoll()));
528
529     UpdateLightDirection();
530   }
531
532   void UpdateLightDirection()
533   {
534     Vector2 tilt = mTiltFilter.Filter();
535     Quaternion q(Radian(tilt.y), Radian(-tilt.x), Radian(0.f));
536     Vector3 lightDir = q.Rotate(INV_LIGHT_DIR);
537     mWaveShader.SetProperty(mUInvLightDir, lightDir);
538   }
539 };
540
541 int DALI_EXPORT_API main( int argc, char **argv )
542 {
543   Application application = Application::New( &argc, &argv, DEMO_THEME_PATH );
544   WavesExample example( application);
545   application.MainLoop();
546   return 0;
547 }