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