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