Added refraction effect demo
[platform/core/uifw/dali-demo.git] / examples / shader-effect / refraction-effect-example.cpp
1 /*
2  * Copyright (c) 2014 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 #include <dali/dali.h>
19 #include <dali-toolkit/dali-toolkit.h>
20 #include "../shared/view.h"
21
22 #include <fstream>
23 #include <sstream>
24
25 using namespace Dali;
26
27 namespace
28 {
29 const char * const APPLICATION_TITLE( "Refraction Effect" );
30 const char * const TOOLBAR_IMAGE( DALI_IMAGE_DIR "top-bar.png" );
31 const char * const CHANGE_TEXTURE_ICON( DALI_IMAGE_DIR "icon-change.png" );
32 const char * const CHANGE_MESH_ICON( DALI_IMAGE_DIR "icon-replace.png" );
33
34 const char* MESH_FILES[] =
35 {
36  DALI_MODEL_DIR "surface_pattern_v01.obj",
37  DALI_MODEL_DIR "surface_pattern_v02.obj"
38 };
39 const unsigned int NUM_MESH_FILES( sizeof( MESH_FILES ) / sizeof( MESH_FILES[0] ) );
40
41 const char* TEXTURE_IMAGES[]=
42 {
43   DALI_IMAGE_DIR "background-1.jpg",
44   DALI_IMAGE_DIR "background-2.jpg",
45   DALI_IMAGE_DIR "background-3.jpg",
46   DALI_IMAGE_DIR "background-4.jpg"
47 };
48 const unsigned int NUM_TEXTURE_IMAGES( sizeof( TEXTURE_IMAGES ) / sizeof( TEXTURE_IMAGES[0] ) );
49
50 #define MAKE_SHADER(A)#A
51
52 struct LightOffsetConstraint
53 {
54   LightOffsetConstraint( float radius )
55   : mRadius( radius )
56   {
57   }
58
59   Vector2 operator()( const Vector2& current, const PropertyInput& spinAngleProperty)
60   {
61     float spinAngle = spinAngleProperty.GetFloat();
62     return Vector2( cos(spinAngle ), sin( spinAngle ) ) * mRadius;
63   }
64
65   float mRadius;
66 };
67
68 } // namespace
69
70 /************************************************************************************************
71  *** This shader is used when the MeshActor is not touched***
72  ************************************************************************************************/
73 class NoEffect : public ShaderEffect
74 {
75 public:
76   /**
77    * Create an empty handle.
78    */
79   NoEffect()
80   {
81   }
82
83   /**
84    * Virtual destructor
85    */
86   virtual ~NoEffect()
87   {
88   }
89
90   /**
91    * Create a NoEffect object.
92    * @return A handle to a newly allocated NoEffect
93    */
94   static NoEffect New()
95   {
96     std::string vertexShader = MAKE_SHADER(
97         uniform mediump vec4 uTextureRect;\n
98         void main()\n
99         {\n
100           gl_Position = uMvpMatrix * vec4( aPosition.xy, 0.0, 1.0 );\n
101           vTexCoord = aTexCoord.xy;\n
102         }\n
103     );
104     std::string fragmentShader = MAKE_SHADER(
105         void main()\n
106         {\n
107           gl_FragColor = texture2D( sTexture, vTexCoord ) * uColor;\n
108         }\n
109     );
110     ShaderEffect shaderEffect = ShaderEffect::New( vertexShader, fragmentShader,
111                                                    GeometryType( GEOMETRY_TYPE_TEXTURED_MESH),
112                                                    ShaderEffect::GeometryHints( ShaderEffect::HINT_NONE ) );
113     NoEffect handle( shaderEffect );
114     return handle;
115   }
116
117 private:
118   /**
119    * Helper for New()
120    */
121   NoEffect( ShaderEffect handle )
122   : ShaderEffect( handle )
123   {
124   }
125 };
126
127 /************************************************************/
128 /* Custom refraction effect shader******************************/
129 /************************************************************/
130
131 class RefractionEffect : public ShaderEffect
132 {
133 public:
134
135   /**
136    * Create an empty RefractionEffect handle.
137    */
138   RefractionEffect()
139   {
140   }
141
142   /**
143    * Virtual destructor
144    */
145   virtual ~RefractionEffect()
146   {
147   }
148
149   /**
150    * Create a RefractionEffect object.
151    * @return A handle to a newly allocated RefractionEffect
152    */
153   static RefractionEffect New()
154   {
155     std::string vertexShader = MAKE_SHADER(
156       varying mediump vec2 vTextureOffset;\n
157       void main()\n
158       {\n
159         gl_Position = uMvpMatrix * vec4( aPosition.xy, 0.0, 1.0 );\n
160         vTexCoord = aTexCoord.xy;\n
161
162         vNormal = aNormal;\n
163         vVertex = vec4( aPosition, 1.0 );\n
164         float length = max(0.01, length(aNormal.xy)) * 40.0;\n
165         vTextureOffset = aNormal.xy / length;\n
166       }\n
167     );
168
169     std::string fragmentShader = MAKE_SHADER(
170       uniform mediump float uEffectStrength;\n
171       uniform mediump vec3 uLightPosition;\n
172       uniform mediump vec2 uLightXYOffset;\n
173       uniform mediump vec2 uLightSpinOffset;\n
174       uniform mediump float uLightIntensity;\n
175       varying mediump vec2 vTextureOffset;\n
176
177       vec3 rgb2hsl(vec3 rgb)\n
178       {\n
179         float epsilon = 1.0e-10;\n
180         vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n
181         vec4 P = mix(vec4(rgb.bg, K.wz), vec4(rgb.gb, K.xy), step(rgb.b, rgb.g));\n
182         vec4 Q = mix(vec4(P.xyw, rgb.r), vec4(rgb.r, P.yzx), step(P.x, rgb.r));\n
183         \n
184         // RGB -> HCV
185         float value = Q.x;\n
186         float chroma = Q.x - min(Q.w, Q.y);\n
187         float hue = abs(Q.z + (Q.w-Q.y) / (6.0*chroma+epsilon));\n
188         // HCV -> HSL
189         float lightness = value - chroma*0.5;\n
190         return vec3( hue, chroma/max( 1.0-abs(lightness*2.0-1.0), 1.0e-1 ), lightness );\n
191       }\n
192
193       vec3 hsl2rgb( vec3 hsl )
194       {
195         // pure hue->RGB
196         vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n
197         vec3 p = abs(fract(hsl.xxx + K.xyz) * 6.0 - K.www);\n
198         vec3 RGB = clamp(p - K.xxx, 0.0, 1.0);\n
199         \n
200         float chroma = ( 1.0 - abs( hsl.z*2.0-1.0 ) ) * hsl.y;\n
201         return ( RGB - 0.5 ) * chroma + hsl.z;
202       }
203
204       void main()\n
205       {\n
206         vec3 normal = normalize( vNormal);\n
207
208         vec3 lightPosition = uLightPosition + vec3(uLightXYOffset+uLightSpinOffset, 0.f);\n
209         mediump vec3 vecToLight = normalize( (lightPosition - vVertex.xyz) * 0.01 );\n
210         mediump float spotEffect = pow( max(0.05, vecToLight.z ) - 0.05, 8.0);\n
211
212         spotEffect = spotEffect * uEffectStrength;\n
213         mediump float lightDiffuse = ( ( dot( vecToLight, normal )-0.75 ) *uLightIntensity  ) * spotEffect;\n
214
215         lowp vec4 color = texture2D( sTexture, vTexCoord + vTextureOffset * spotEffect );\n
216         vec3 lightedColor =  hsl2rgb( rgb2hsl(color.rgb) + vec3(0.0,0.0,lightDiffuse) );\n
217
218         gl_FragColor = vec4( lightedColor, color.a ) * uColor;\n
219       }\n
220     );
221
222     ShaderEffect shaderEffect = ShaderEffect::New( vertexShader, fragmentShader,
223                                                    GeometryType( GEOMETRY_TYPE_TEXTURED_MESH),
224                                                    ShaderEffect::GeometryHints( ShaderEffect::HINT_BLENDING ) );
225     RefractionEffect handle( shaderEffect );
226
227     Vector2 stageSize = Stage::GetCurrent().GetSize();
228     handle.SetLightPosition( Vector2(stageSize.x, 0.f) );
229     handle.SetUniform( "uLightXYOffset",  Vector2::ZERO );
230     handle.SetUniform( "uLightSpinOffset",  Vector2::ZERO );
231     handle.SetUniform( "uEffectStrength", 0.f );
232     handle.SetUniform( "uLightIntensity",  2.5f );
233
234     Property::Index index = handle.RegisterProperty( "uSpinAngle", 0.f );
235     Constraint constraint = Constraint::New<Vector2>( handle.GetPropertyIndex("uLightSpinOffset"),
236                                                       LocalSource(index),
237                                                       LightOffsetConstraint(stageSize.x*0.1f));
238     handle.ApplyConstraint( constraint );
239
240     return handle;
241   }
242
243   void SetLightPosition( const Vector2& position )
244   {
245     Vector2 stageHalfSize = Stage::GetCurrent().GetSize() * 0.5f;
246     SetUniform( "uLightPosition", Vector3( position.x - stageHalfSize.x, position.y - stageHalfSize.y, stageHalfSize.x ) );
247   }
248
249   void SetLightXYOffset( const Vector2& offset )
250   {
251     SetUniform( "uLightXYOffset",  offset );
252   }
253
254   void SetEffectStrength( float strength )
255   {
256     SetUniform( "uEffectStrength", strength );
257   }
258
259   void SetLightIntensity( float intensity )
260   {
261     SetUniform( "uLightIntensity", intensity );
262   }
263
264 private:
265   /**
266    * Helper for New()
267    */
268   RefractionEffect( ShaderEffect handle )
269   : ShaderEffect( handle )
270   {
271   }
272 };
273
274 /*************************************************/
275 /*Demo using RefractionEffect*****************/
276 /*************************************************/
277 class RefractionEffectExample : public ConnectionTracker
278 {
279 public:
280   RefractionEffectExample( Application &application )
281   : mApplication( application ),
282     mIsDown( false ),
283     mCurrentTextureId( 1 ),
284     mCurrentMeshId( 0 )
285   {
286     // Connect to the Application's Init signal
287     application.InitSignal().Connect(this, &RefractionEffectExample::Create);
288   }
289
290   ~RefractionEffectExample()
291   {
292   }
293
294 private:
295
296   // The Init signal is received once (only) during the Application lifetime
297   void Create(Application& application)
298   {
299     Stage stage = Stage::GetCurrent();
300     mStageHalfSize = stage.GetSize() * 0.5f;
301
302     stage.KeyEventSignal().Connect(this, &RefractionEffectExample::OnKeyEvent);
303
304     // Creates a default view with a default tool bar.
305     // The view is added to the stage.
306     Toolkit::ToolBar toolBar;
307     Toolkit::View    view;
308     mContent = DemoHelper::CreateView( application,
309         view,
310         toolBar,
311         "",
312         TOOLBAR_IMAGE,
313         APPLICATION_TITLE );
314
315     // Add a button to change background. (right of toolbar)
316     mChangeTextureButton = Toolkit::PushButton::New();
317     mChangeTextureButton.SetBackgroundImage( Image::New( CHANGE_TEXTURE_ICON ) );
318     mChangeTextureButton.ClickedSignal().Connect( this, &RefractionEffectExample::OnChangeTexture );
319     toolBar.AddControl( mChangeTextureButton,
320                         DemoHelper::DEFAULT_VIEW_STYLE.mToolBarButtonPercentage,
321                         Toolkit::Alignment::HorizontalRight,
322                         DemoHelper::DEFAULT_MODE_SWITCH_PADDING  );
323     // Add a button to change mesh pattern. ( left of bar )
324     mChangeMeshButton = Toolkit::PushButton::New();
325     mChangeMeshButton.SetBackgroundImage( Image::New( CHANGE_MESH_ICON ) );
326     mChangeMeshButton.ClickedSignal().Connect( this, &RefractionEffectExample::OnChangeMesh );
327     toolBar.AddControl( mChangeMeshButton,
328                         DemoHelper::DEFAULT_VIEW_STYLE.mToolBarButtonPercentage,
329                         Toolkit::Alignment::HorizontalLeft,
330                         DemoHelper::DEFAULT_MODE_SWITCH_PADDING  );
331
332     // creates the shader effects applied on the mesh actor
333     mRefractionEffect = RefractionEffect::New(); // used when the finger is touching the screen
334     mNoEffect = NoEffect::New(); // used in the other situations, basic render shader
335     // Create the mesh from the obj file and add to stage
336     mMaterial =  Material::New( "Material" ) ;
337     mMaterial.SetDiffuseTexture(Image::New(TEXTURE_IMAGES[mCurrentTextureId]));
338     CreateSurface( MESH_FILES[mCurrentMeshId] );
339
340     // Connect the callback to the touch signal on the mesh actor
341     mContent.TouchedSignal().Connect( this, &RefractionEffectExample::OnTouch );
342
343     // the animation which spin the light around the finger touch position
344     mLightPosition = Vector2( mStageHalfSize.x*2.f, 0.f);
345     mLightAnimation = Animation::New(2.f);
346     mLightAnimation.AnimateTo( Property( mRefractionEffect, "uSpinAngle" ), Math::PI*2.f );
347     mLightAnimation.SetLooping( true );
348     mLightAnimation.Pause();
349   }
350
351   /**
352    * Create a mesh actor with different geometry to replace the current one
353    */
354   bool OnChangeMesh( Toolkit::Button button  )
355   {
356     if( mMeshActor )
357     {
358       UnparentAndReset( mMeshActor );
359     }
360
361     mCurrentMeshId = ( mCurrentMeshId + 1 ) % NUM_MESH_FILES;
362     CreateSurface( MESH_FILES[mCurrentMeshId] );
363
364     return true;
365   }
366
367   bool OnChangeTexture( Toolkit::Button button )
368   {
369     mCurrentTextureId = ( mCurrentTextureId + 1 ) % NUM_TEXTURE_IMAGES;
370     mMaterial.SetDiffuseTexture(Image::New(TEXTURE_IMAGES[mCurrentTextureId]));
371
372     return true;
373   }
374
375   bool OnTouch( Actor actor , const TouchEvent& event )
376   {
377     const TouchPoint &point = event.GetPoint(0);
378
379     switch(point.state)
380     {
381       case TouchPoint::Down:
382       {
383         mIsDown = true;
384         mDownPosition = point.screen;
385
386         mLightAnimation.Play();
387
388         if( mStrenghAnimation )
389         {
390           mStrenghAnimation.Clear();
391         }
392
393         mRefractionEffect.SetLightXYOffset( point.screen - mLightPosition );
394         mMeshActor.SetShaderEffect( mRefractionEffect );
395         mStrenghAnimation= Animation::New(0.5f);
396         mStrenghAnimation.AnimateTo( Property( mRefractionEffect, "uEffectStrength" ), 1.f );
397         mStrenghAnimation.Play();
398
399         break;
400       }
401       case TouchPoint::Motion:
402       {
403         if(mIsDown)
404         {
405           // make the light position following the finger movement
406           mRefractionEffect.SetLightXYOffset( point.screen - mLightPosition );
407         }
408         break;
409       }
410       case TouchPoint::Up:
411       case TouchPoint::Leave:
412       case TouchPoint::Interrupted:
413       {
414         if(mIsDown)
415         {
416           mLightAnimation.Pause();
417
418           if( mStrenghAnimation )
419           {
420             mStrenghAnimation.Clear();
421           }
422           mStrenghAnimation = Animation::New(0.5f);
423           mStrenghAnimation.AnimateTo( Property( mRefractionEffect, "uEffectStrength" ), 0.f );
424           mStrenghAnimation.FinishedSignal().Connect( this, &RefractionEffectExample::OnTouchFinished );
425           mStrenghAnimation.Play();
426         }
427
428         mIsDown = false;
429         break;
430       }
431       case TouchPoint::Stationary:
432       case TouchPoint::Last:
433       default:
434       {
435         break;
436       }
437     }
438     return true;
439   }
440
441   void OnTouchFinished( Animation& source )
442   {
443     mMeshActor.SetShaderEffect( mNoEffect );
444     mRefractionEffect.SetLightXYOffset( Vector2::ZERO );
445   }
446
447   void CreateSurface( const std::string& objFileName )
448   {
449     MeshData::VertexContainer    vertices;
450     MeshData::FaceIndices        faces;
451     MeshData                     meshData;
452
453     std::vector<float> boundingBox;
454     std::vector<Vector3> vertexPositions;
455     std::vector<int> faceIndices;
456     // read the vertice and faces from the .obj file, and record the bounding box
457     ReadObjFile( objFileName, boundingBox, vertexPositions, faceIndices );
458
459     std::vector<Vector2> textureCoordinates;
460     // align the mesh, scale it to fit the screen size, and calculate the texture coordinate for each vertex
461     ShapeResizeAndTexureCoordinateCalculation( boundingBox, vertexPositions, textureCoordinates );
462
463     // re-organize the mesh, the vertices are duplicated, each vertex only belongs to one triangle.
464     // Without sharing vertex between triangle, so we can manipulate the texture offset on each triangle conveniently.
465     for( std::size_t i=0; i<faceIndices.size(); i=i+3 )
466     {
467       Vector3 edge1 = vertexPositions[ faceIndices[i+2] ] - vertexPositions[ faceIndices[i] ];
468       Vector3 edge2 = vertexPositions[ faceIndices[i+1] ] - vertexPositions[ faceIndices[i] ];
469       Vector3 normal = edge1.Cross(edge2);
470       normal.Normalize();
471
472       if( normal.z > 0 )
473       {
474         faces.push_back( i );
475         faces.push_back( i+1 );
476         faces.push_back( i+2 );
477       }
478       else
479       {
480         normal *= -1.f;
481         faces.push_back( i );
482         faces.push_back( i+2 );
483         faces.push_back( i+1 );
484       }
485
486       vertices.push_back( MeshData::Vertex( vertexPositions[ faceIndices[i] ], textureCoordinates[ faceIndices[i] ], normal ) );
487       vertices.push_back( MeshData::Vertex( vertexPositions[ faceIndices[i+1] ], textureCoordinates[ faceIndices[i+1] ], normal ) );
488       vertices.push_back( MeshData::Vertex( vertexPositions[ faceIndices[i+2] ], textureCoordinates[ faceIndices[i+2] ], normal ) );
489
490     }
491
492     // Now ready to construct the mesh actor
493     meshData.SetMaterial( mMaterial );
494     meshData.SetVertices( vertices );
495     meshData.SetFaceIndices( faces );
496     meshData.SetHasTextureCoords(true);
497     meshData.SetHasNormals(true);
498     mMeshActor = MeshActor::New( Mesh::New( meshData ) );
499     mMeshActor.SetParentOrigin(ParentOrigin::CENTER);
500     mMeshActor.SetAffectedByLighting( false );
501     mMeshActor.SetShaderEffect( mNoEffect );
502     mContent.Add( mMeshActor );
503   }
504
505   void ReadObjFile( const std::string& objFileName,
506       std::vector<float>& boundingBox,
507       std::vector<Vector3>& vertexPositions,
508       std::vector<int>& faceIndices)
509   {
510     std::ifstream ifs( objFileName.c_str(), std::ios::in );
511
512     boundingBox.resize( 6 );
513     boundingBox[0]=boundingBox[2]=boundingBox[4] = std::numeric_limits<float>::max();
514     boundingBox[1]=boundingBox[3]=boundingBox[5] = -std::numeric_limits<float>::max();
515
516     std::string line;
517     while( std::getline( ifs, line ) )
518     {
519       if( line[0] == 'v' && std::isspace(line[1]))  // vertex
520       {
521         std::istringstream iss(line.substr(2), std::istringstream::in);
522         unsigned int i = 0;
523         Vector3 vertex;
524         while( iss >> vertex[i++] && i < 3);
525         if( vertex.x < boundingBox[0] )  boundingBox[0] = vertex.x;
526         if( vertex.x > boundingBox[1] )  boundingBox[1] = vertex.x;
527         if( vertex.y < boundingBox[2] )  boundingBox[2] = vertex.y;
528         if( vertex.y > boundingBox[3] )  boundingBox[3] = vertex.y;
529         if( vertex.z < boundingBox[4] )  boundingBox[4] = vertex.z;
530         if( vertex.z > boundingBox[5] )  boundingBox[5] = vertex.z;
531         vertexPositions.push_back( vertex );
532       }
533       else if( line[0] == 'f' ) //face
534       {
535         unsigned int numOfInt = 3;
536         while( true )
537         {
538           std::size_t found  = line.find('/');
539           if( found == std::string::npos )
540           {
541             break;
542           }
543           line[found] = ' ';
544           numOfInt++;
545         }
546
547         std::istringstream iss(line.substr(2), std::istringstream::in);
548         int indices[ numOfInt ];
549         unsigned int i=0;
550         while( iss >> indices[i++] && i < numOfInt);
551         unsigned int step = (i+1) / 3;
552         faceIndices.push_back( indices[0]-1 );
553         faceIndices.push_back( indices[step]-1 );
554         faceIndices.push_back( indices[2*step]-1 );
555       }
556     }
557
558     ifs.close();
559   }
560
561   void ShapeResizeAndTexureCoordinateCalculation( const std::vector<float>& boundingBox,
562       std::vector<Vector3>& vertexPositions,
563       std::vector<Vector2>& textureCoordinates)
564   {
565     Vector3 bBoxSize( boundingBox[1] - boundingBox[0], boundingBox[3] - boundingBox[2], boundingBox[5] - boundingBox[4]);
566     Vector3 bBoxMinCorner( boundingBox[0], boundingBox[2], boundingBox[4] );
567
568     Vector2 stageSize = Stage::GetCurrent().GetSize();
569     Vector3 scale( stageSize.x / bBoxSize.x, stageSize.y / bBoxSize.y, 1.f );
570     scale.z = (scale.x + scale.y)/2.f;
571
572     for( std::vector<Vector3>::iterator iter = vertexPositions.begin(); iter != vertexPositions.end(); iter++ )
573     {
574       Vector3 newPosition(  (*iter) - bBoxMinCorner ) ;
575
576       Vector2 textureCoord( newPosition.x / bBoxSize.x, newPosition.y / bBoxSize.y );
577       textureCoordinates.push_back( textureCoord );
578
579       newPosition -= bBoxSize * 0.5f;
580       (*iter) = newPosition * scale;
581     }
582   }
583
584   /**
585    * Main key event handler
586    */
587   void OnKeyEvent(const KeyEvent& event)
588   {
589     if(event.state == KeyEvent::Down)
590     {
591       if( IsKey( event, Dali::DALI_KEY_ESCAPE) || IsKey( event, Dali::DALI_KEY_BACK) )
592       {
593         mApplication.Quit();
594       }
595     }
596   }
597
598 private:
599
600   Application&   mApplication;
601   Layer          mContent;
602
603   bool           mIsDown;
604   Vector2        mDownPosition;
605   Vector2        mLightPosition;
606   Vector2        mStageHalfSize;
607
608   Material       mMaterial;
609   MeshActor      mMeshActor;
610
611   RefractionEffect  mRefractionEffect;
612   NoEffect          mNoEffect;
613   Animation         mLightAnimation;
614   Animation         mStrenghAnimation;
615
616   Toolkit::PushButton        mChangeTextureButton;
617   Toolkit::PushButton        mChangeMeshButton;
618   unsigned int               mCurrentTextureId;
619   unsigned int               mCurrentMeshId;
620 };
621
622 /*****************************************************************************/
623
624 static void
625 RunTest(Application& app)
626 {
627   RefractionEffectExample theApp(app);
628   app.MainLoop();
629 }
630
631 /*****************************************************************************/
632
633 int
634 main(int argc, char **argv)
635 {
636   Application app = Application::New(&argc, &argv);
637
638   RunTest(app);
639
640   return 0;
641 }