Convert shaders in dali-demo to use shader compilation tool
[platform/core/uifw/dali-demo.git] / examples / refraction-effect / refraction-effect-example.cpp
1 /*
2  * Copyright (c) 2020 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 <dali-toolkit/dali-toolkit.h>
20 #include <dali/dali.h>
21 #include <dali/devel-api/adaptor-framework/file-loader.h>
22 #include <dali/integration-api/debug.h>
23
24 #include <cctype>
25 #include <limits>
26 #include <sstream>
27
28 // INTERNAL INCLUDES
29 #include "shared/utility.h"
30 #include "shared/view.h"
31 #include "generated/refraction-effect-flat-vert.h"
32 #include "generated/refraction-effect-flat-frag.h"
33 #include "generated/refraction-effect-refraction-vert.h"
34 #include "generated/refraction-effect-refraction-frag.h"
35
36 using namespace Dali;
37
38 namespace
39 {
40 const char* const APPLICATION_TITLE("Refraction Effect");
41 const char* const TOOLBAR_IMAGE(DEMO_IMAGE_DIR "top-bar.png");
42 const char* const CHANGE_TEXTURE_ICON(DEMO_IMAGE_DIR "icon-change.png");
43 const char* const CHANGE_TEXTURE_ICON_SELECTED(DEMO_IMAGE_DIR "icon-change-selected.png");
44 const char* const CHANGE_MESH_ICON(DEMO_IMAGE_DIR "icon-replace.png");
45 const char* const CHANGE_MESH_ICON_SELECTED(DEMO_IMAGE_DIR "icon-replace-selected.png");
46
47 const char* MESH_FILES[] =
48   {
49     DEMO_MODEL_DIR "surface_pattern_v01.obj",
50     DEMO_MODEL_DIR "surface_pattern_v02.obj"};
51 const unsigned int NUM_MESH_FILES(sizeof(MESH_FILES) / sizeof(MESH_FILES[0]));
52
53 const char* TEXTURE_IMAGES[] =
54   {
55     DEMO_IMAGE_DIR "background-1.jpg",
56     DEMO_IMAGE_DIR "background-2.jpg",
57     DEMO_IMAGE_DIR "background-3.jpg",
58     DEMO_IMAGE_DIR "background-4.jpg"};
59 const unsigned int NUM_TEXTURE_IMAGES(sizeof(TEXTURE_IMAGES) / sizeof(TEXTURE_IMAGES[0]));
60
61 struct LightOffsetConstraint
62 {
63   LightOffsetConstraint(float radius)
64   : mRadius(radius)
65   {
66   }
67
68   void operator()(Vector2& current, const PropertyInputContainer& inputs)
69   {
70     float spinAngle = inputs[0]->GetFloat();
71     current.x       = cos(spinAngle);
72     current.y       = sin(spinAngle);
73
74     current *= mRadius;
75   }
76
77   float mRadius;
78 };
79
80 /**
81  * structure of the vertex in the mesh
82  */
83 struct Vertex
84 {
85   Vector3 position;
86   Vector3 normal;
87   Vector2 textureCoord;
88
89   Vertex()
90   {
91   }
92
93   Vertex(const Vector3& position, const Vector3& normal, const Vector2& textureCoord)
94   : position(position),
95     normal(normal),
96     textureCoord(textureCoord)
97   {
98   }
99 };
100
101 } // namespace
102
103 /*************************************************/
104 /*Demo using RefractionEffect*****************/
105 /*************************************************/
106 class RefractionEffectExample : public ConnectionTracker
107 {
108 public:
109   RefractionEffectExample(Application& application)
110   : mApplication(application),
111     mContent(),
112     mTextureSet(),
113     mGeometry(),
114     mRenderer(),
115     mMeshActor(),
116     mShaderFlat(),
117     mShaderRefraction(),
118     mLightAnimation(),
119     mStrenghAnimation(),
120     mLightXYOffsetIndex(Property::INVALID_INDEX),
121     mSpinAngleIndex(Property::INVALID_INDEX),
122     mLightIntensityIndex(Property::INVALID_INDEX),
123     mEffectStrengthIndex(Property::INVALID_INDEX),
124     mChangeTextureButton(),
125     mChangeMeshButton(),
126     mCurrentTextureId(1),
127     mCurrentMeshId(0)
128   {
129     // Connect to the Application's Init signal
130     application.InitSignal().Connect(this, &RefractionEffectExample::Create);
131   }
132
133   ~RefractionEffectExample()
134   {
135   }
136
137 private:
138   // The Init signal is received once (only) during the Application lifetime
139   void Create(Application& application)
140   {
141     Window  window     = application.GetWindow();
142     Vector2 windowSize = window.GetSize();
143
144     window.KeyEventSignal().Connect(this, &RefractionEffectExample::OnKeyEvent);
145
146     // Creates a default view with a default tool bar.
147     // The view is added to the window.
148     Toolkit::ToolBar toolBar;
149     Toolkit::Control view;
150     mContent = DemoHelper::CreateView(application,
151                                       view,
152                                       toolBar,
153                                       "",
154                                       TOOLBAR_IMAGE,
155                                       APPLICATION_TITLE);
156
157     // Add a button to change background. (right of toolbar)
158     mChangeTextureButton = Toolkit::PushButton::New();
159     mChangeTextureButton.SetProperty(Toolkit::Button::Property::UNSELECTED_BACKGROUND_VISUAL, CHANGE_TEXTURE_ICON);
160     mChangeTextureButton.SetProperty(Toolkit::Button::Property::SELECTED_BACKGROUND_VISUAL, CHANGE_TEXTURE_ICON_SELECTED);
161     mChangeTextureButton.ClickedSignal().Connect(this, &RefractionEffectExample::OnChangeTexture);
162     toolBar.AddControl(mChangeTextureButton,
163                        DemoHelper::DEFAULT_VIEW_STYLE.mToolBarButtonPercentage,
164                        Toolkit::Alignment::HORIZONTAL_RIGHT,
165                        DemoHelper::DEFAULT_MODE_SWITCH_PADDING);
166     // Add a button to change mesh pattern. ( left of bar )
167     mChangeMeshButton = Toolkit::PushButton::New();
168     mChangeMeshButton.SetProperty(Toolkit::Button::Property::UNSELECTED_BACKGROUND_VISUAL, CHANGE_MESH_ICON);
169     mChangeMeshButton.SetProperty(Toolkit::Button::Property::SELECTED_BACKGROUND_VISUAL, CHANGE_MESH_ICON_SELECTED);
170     mChangeMeshButton.ClickedSignal().Connect(this, &RefractionEffectExample::OnChangeMesh);
171     toolBar.AddControl(mChangeMeshButton,
172                        DemoHelper::DEFAULT_VIEW_STYLE.mToolBarButtonPercentage,
173                        Toolkit::Alignment::HORIZONTAL_LEFT,
174                        DemoHelper::DEFAULT_MODE_SWITCH_PADDING);
175
176     // shader used when the screen is not touched, render a flat surface
177     mShaderFlat = Shader::New(SHADER_REFRACTION_EFFECT_FLAT_VERT, SHADER_REFRACTION_EFFECT_FLAT_FRAG);
178     mGeometry   = CreateGeometry(MESH_FILES[mCurrentMeshId]);
179
180     Texture texture = DemoHelper::LoadWindowFillingTexture(window.GetSize(), TEXTURE_IMAGES[mCurrentTextureId]);
181     mTextureSet     = TextureSet::New();
182     mTextureSet.SetTexture(0u, texture);
183
184     mRenderer = Renderer::New(mGeometry, mShaderFlat);
185     mRenderer.SetTextures(mTextureSet);
186
187     mMeshActor = Actor::New();
188     mMeshActor.AddRenderer(mRenderer);
189     mMeshActor.SetProperty(Actor::Property::SIZE, windowSize);
190     mMeshActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
191     mContent.Add(mMeshActor);
192
193     // Connect the callback to the touch signal on the mesh actor
194     mContent.TouchedSignal().Connect(this, &RefractionEffectExample::OnTouch);
195
196     // shader used when the finger is touching the screen. render refraction effect
197     mShaderRefraction = Shader::New(SHADER_REFRACTION_EFFECT_REFRACTION_VERT, SHADER_REFRACTION_EFFECT_REFRACTION_FRAG);
198
199     // register uniforms
200     mLightXYOffsetIndex = mMeshActor.RegisterProperty("uLightXYOffset", Vector2::ZERO);
201
202     mLightIntensityIndex = mMeshActor.RegisterProperty("uLightIntensity", 2.5f);
203
204     mEffectStrengthIndex = mMeshActor.RegisterProperty("uEffectStrength", 0.f);
205
206     Vector3 lightPosition(-windowSize.x * 0.5f, -windowSize.y * 0.5f, windowSize.x * 0.5f); // top_left
207     mMeshActor.RegisterProperty("uLightPosition", lightPosition);
208
209     Property::Index lightSpinOffsetIndex = mMeshActor.RegisterProperty("uLightSpinOffset", Vector2::ZERO);
210
211     mSpinAngleIndex       = mMeshActor.RegisterProperty("uSpinAngle", 0.f);
212     Constraint constraint = Constraint::New<Vector2>(mMeshActor, lightSpinOffsetIndex, LightOffsetConstraint(windowSize.x * 0.1f));
213     constraint.AddSource(LocalSource(mSpinAngleIndex));
214     constraint.Apply();
215
216     // the animation which spin the light around the finger touch position
217     mLightAnimation = Animation::New(2.f);
218     mLightAnimation.AnimateTo(Property(mMeshActor, mSpinAngleIndex), Math::PI * 2.f);
219     mLightAnimation.SetLooping(true);
220     mLightAnimation.Pause();
221   }
222
223   void SetLightXYOffset(const Vector2& offset)
224   {
225     mMeshActor.SetProperty(mLightXYOffsetIndex, offset);
226   }
227
228   /**
229    * Create a mesh actor with different geometry to replace the current one
230    */
231   bool OnChangeMesh(Toolkit::Button button)
232   {
233     mCurrentMeshId = (mCurrentMeshId + 1) % NUM_MESH_FILES;
234     mGeometry      = CreateGeometry(MESH_FILES[mCurrentMeshId]);
235     mRenderer.SetGeometry(mGeometry);
236
237     return true;
238   }
239
240   bool OnChangeTexture(Toolkit::Button button)
241   {
242     mCurrentTextureId = (mCurrentTextureId + 1) % NUM_TEXTURE_IMAGES;
243     Texture texture   = DemoHelper::LoadWindowFillingTexture(mApplication.GetWindow().GetSize(), TEXTURE_IMAGES[mCurrentTextureId]);
244     mTextureSet.SetTexture(0u, texture);
245     return true;
246   }
247
248   bool OnTouch(Actor actor, const TouchEvent& event)
249   {
250     switch(event.GetState(0))
251     {
252       case PointState::DOWN:
253       {
254         mRenderer.SetShader(mShaderRefraction);
255
256         SetLightXYOffset(event.GetScreenPosition(0));
257
258         mLightAnimation.Play();
259
260         if(mStrenghAnimation)
261         {
262           mStrenghAnimation.Clear();
263         }
264
265         mStrenghAnimation = Animation::New(0.5f);
266         mStrenghAnimation.AnimateTo(Property(mMeshActor, mEffectStrengthIndex), 1.f);
267         mStrenghAnimation.Play();
268
269         break;
270       }
271       case PointState::MOTION:
272       {
273         // make the light position following the finger movement
274         SetLightXYOffset(event.GetScreenPosition(0));
275         break;
276       }
277       case PointState::UP:
278       case PointState::LEAVE:
279       case PointState::INTERRUPTED:
280       {
281         mLightAnimation.Pause();
282
283         if(mStrenghAnimation)
284         {
285           mStrenghAnimation.Clear();
286         }
287         mStrenghAnimation = Animation::New(0.5f);
288         mStrenghAnimation.AnimateTo(Property(mMeshActor, mEffectStrengthIndex), 0.f);
289         mStrenghAnimation.FinishedSignal().Connect(this, &RefractionEffectExample::OnTouchFinished);
290         mStrenghAnimation.Play();
291         break;
292       }
293       case PointState::STATIONARY:
294       {
295         break;
296       }
297     }
298
299     return true;
300   }
301
302   void OnTouchFinished(Animation& source)
303   {
304     mRenderer.SetShader(mShaderFlat);
305     SetLightXYOffset(Vector2::ZERO);
306   }
307
308   Geometry CreateGeometry(const std::string& objFileName)
309   {
310     std::vector<Vector3> vertexPositions;
311     Vector<unsigned int> faceIndices;
312     Vector<float>        boundingBox;
313     // read the vertice and faces from the .obj file, and record the bounding box
314     ReadObjFile(objFileName, boundingBox, vertexPositions, faceIndices);
315
316     std::vector<Vector2> textureCoordinates;
317     // align the mesh, scale it to fit the screen size, and calculate the texture coordinate for each vertex
318     ShapeResizeAndTexureCoordinateCalculation(boundingBox, vertexPositions, textureCoordinates);
319
320     // re-organize the mesh, the vertices are duplicated, each vertex only belongs to one triangle.
321     // Without sharing vertex between triangle, so we can manipulate the texture offset on each triangle conveniently.
322     std::vector<Vertex> vertices;
323
324     std::size_t size = faceIndices.Size();
325     vertices.reserve(size);
326
327     for(std::size_t i = 0; i < size; i = i + 3)
328     {
329       Vector3 edge1  = vertexPositions[faceIndices[i + 2]] - vertexPositions[faceIndices[i]];
330       Vector3 edge2  = vertexPositions[faceIndices[i + 1]] - vertexPositions[faceIndices[i]];
331       Vector3 normal = edge1.Cross(edge2);
332       normal.Normalize();
333
334       // make sure all the faces are front-facing
335       if(normal.z > 0)
336       {
337         vertices.push_back(Vertex(vertexPositions[faceIndices[i]], normal, textureCoordinates[faceIndices[i]]));
338         vertices.push_back(Vertex(vertexPositions[faceIndices[i + 1]], normal, textureCoordinates[faceIndices[i + 1]]));
339         vertices.push_back(Vertex(vertexPositions[faceIndices[i + 2]], normal, textureCoordinates[faceIndices[i + 2]]));
340       }
341       else
342       {
343         normal *= -1.f;
344         vertices.push_back(Vertex(vertexPositions[faceIndices[i]], normal, textureCoordinates[faceIndices[i]]));
345         vertices.push_back(Vertex(vertexPositions[faceIndices[i + 2]], normal, textureCoordinates[faceIndices[i + 2]]));
346         vertices.push_back(Vertex(vertexPositions[faceIndices[i + 1]], normal, textureCoordinates[faceIndices[i + 1]]));
347       }
348     }
349
350     Property::Map vertexFormat;
351     vertexFormat["aPosition"]    = Property::VECTOR3;
352     vertexFormat["aNormal"]      = Property::VECTOR3;
353     vertexFormat["aTexCoord"]    = Property::VECTOR2;
354     VertexBuffer surfaceVertices = VertexBuffer::New(vertexFormat);
355     surfaceVertices.SetData(&vertices[0], vertices.size());
356
357     Geometry surface = Geometry::New();
358     surface.AddVertexBuffer(surfaceVertices);
359
360     return surface;
361   }
362
363   void ReadObjFile(const std::string&    objFileName,
364                    Vector<float>&        boundingBox,
365                    std::vector<Vector3>& vertexPositions,
366                    Vector<unsigned int>& faceIndices)
367   {
368     std::streampos     bufferSize = 0;
369     Dali::Vector<char> fileBuffer;
370     if(!Dali::FileLoader::ReadFile(objFileName, bufferSize, fileBuffer, Dali::FileLoader::FileType::TEXT))
371     {
372       DALI_LOG_WARNING("file open failed for: \"%s\"", objFileName.c_str());
373       return;
374     }
375
376     fileBuffer.PushBack('\0');
377
378     std::stringstream iss(&fileBuffer[0], std::ios::in);
379
380     boundingBox.Resize(6);
381     boundingBox[0] = boundingBox[2] = boundingBox[4] = std::numeric_limits<float>::max();
382     boundingBox[1] = boundingBox[3] = boundingBox[5] = -std::numeric_limits<float>::max();
383
384     std::string line;
385     while(std::getline(iss, line))
386     {
387       if(line[0] == 'v' && std::isspace(line[1])) // vertex
388       {
389         std::istringstream iss(line.substr(2), std::istringstream::in);
390         unsigned int       i = 0;
391         Vector3            vertex;
392         while(iss >> vertex[i++] && i < 3)
393           ;
394         if(vertex.x < boundingBox[0]) boundingBox[0] = vertex.x;
395         if(vertex.x > boundingBox[1]) boundingBox[1] = vertex.x;
396         if(vertex.y < boundingBox[2]) boundingBox[2] = vertex.y;
397         if(vertex.y > boundingBox[3]) boundingBox[3] = vertex.y;
398         if(vertex.z < boundingBox[4]) boundingBox[4] = vertex.z;
399         if(vertex.z > boundingBox[5]) boundingBox[5] = vertex.z;
400         vertexPositions.push_back(vertex);
401       }
402       else if(line[0] == 'f') //face
403       {
404         unsigned int numOfInt = 3;
405         while(true)
406         {
407           std::size_t found = line.find('/');
408           if(found == std::string::npos)
409           {
410             break;
411           }
412           line[found] = ' ';
413           numOfInt++;
414         }
415
416         std::istringstream         iss(line.substr(2), std::istringstream::in);
417         Dali::Vector<unsigned int> indices;
418         indices.Resize(numOfInt);
419         unsigned int i = 0;
420         while(iss >> indices[i++] && i < numOfInt)
421           ;
422         unsigned int step = (i + 1) / 3;
423         faceIndices.PushBack(indices[0] - 1);
424         faceIndices.PushBack(indices[step] - 1);
425         faceIndices.PushBack(indices[2 * step] - 1);
426       }
427     }
428   }
429
430   void ShapeResizeAndTexureCoordinateCalculation(const Vector<float>&  boundingBox,
431                                                  std::vector<Vector3>& vertexPositions,
432                                                  std::vector<Vector2>& textureCoordinates)
433   {
434     Vector3 bBoxSize(boundingBox[1] - boundingBox[0], boundingBox[3] - boundingBox[2], boundingBox[5] - boundingBox[4]);
435     Vector3 bBoxMinCorner(boundingBox[0], boundingBox[2], boundingBox[4]);
436
437     Vector2 windowSize = mApplication.GetWindow().GetSize();
438     Vector3 scale(windowSize.x / bBoxSize.x, windowSize.y / bBoxSize.y, 1.f);
439     scale.z = (scale.x + scale.y) / 2.f;
440
441     textureCoordinates.reserve(vertexPositions.size());
442
443     for(std::vector<Vector3>::iterator iter = vertexPositions.begin(); iter != vertexPositions.end(); iter++)
444     {
445       Vector3 newPosition((*iter) - bBoxMinCorner);
446
447       textureCoordinates.push_back(Vector2(newPosition.x / bBoxSize.x, newPosition.y / bBoxSize.y));
448
449       newPosition -= bBoxSize * 0.5f;
450       (*iter) = newPosition * scale;
451     }
452   }
453
454   /**
455    * Main key event handler
456    */
457   void OnKeyEvent(const KeyEvent& event)
458   {
459     if(event.GetState() == KeyEvent::DOWN)
460     {
461       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
462       {
463         mApplication.Quit();
464       }
465     }
466   }
467
468 private:
469   Application& mApplication;
470   Layer        mContent;
471   TextureSet   mTextureSet;
472   Geometry     mGeometry;
473   Renderer     mRenderer;
474   Actor        mMeshActor;
475
476   Shader mShaderFlat;
477   Shader mShaderRefraction;
478
479   Animation mLightAnimation;
480   Animation mStrenghAnimation;
481
482   Property::Index mLightXYOffsetIndex;
483   Property::Index mSpinAngleIndex;
484   Property::Index mLightIntensityIndex;
485   Property::Index mEffectStrengthIndex;
486
487   Toolkit::PushButton mChangeTextureButton;
488   Toolkit::PushButton mChangeMeshButton;
489   unsigned int        mCurrentTextureId;
490   unsigned int        mCurrentMeshId;
491 };
492
493 /*****************************************************************************/
494
495 int DALI_EXPORT_API main(int argc, char** argv)
496 {
497   Application             app = Application::New(&argc, &argv, DEMO_THEME_PATH);
498   RefractionEffectExample theApp(app);
499   app.MainLoop();
500   return 0;
501 }