2 * Copyright (c) 2020 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
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>
29 #include "shared/utility.h"
30 #include "shared/view.h"
36 const char* const APPLICATION_TITLE("Refraction Effect");
37 const char* const TOOLBAR_IMAGE(DEMO_IMAGE_DIR "top-bar.png");
38 const char* const CHANGE_TEXTURE_ICON(DEMO_IMAGE_DIR "icon-change.png");
39 const char* const CHANGE_TEXTURE_ICON_SELECTED(DEMO_IMAGE_DIR "icon-change-selected.png");
40 const char* const CHANGE_MESH_ICON(DEMO_IMAGE_DIR "icon-replace.png");
41 const char* const CHANGE_MESH_ICON_SELECTED(DEMO_IMAGE_DIR "icon-replace-selected.png");
43 const char* MESH_FILES[] =
45 DEMO_MODEL_DIR "surface_pattern_v01.obj",
46 DEMO_MODEL_DIR "surface_pattern_v02.obj"};
47 const unsigned int NUM_MESH_FILES(sizeof(MESH_FILES) / sizeof(MESH_FILES[0]));
49 const char* TEXTURE_IMAGES[] =
51 DEMO_IMAGE_DIR "background-1.jpg",
52 DEMO_IMAGE_DIR "background-2.jpg",
53 DEMO_IMAGE_DIR "background-3.jpg",
54 DEMO_IMAGE_DIR "background-4.jpg"};
55 const unsigned int NUM_TEXTURE_IMAGES(sizeof(TEXTURE_IMAGES) / sizeof(TEXTURE_IMAGES[0]));
57 struct LightOffsetConstraint
59 LightOffsetConstraint(float radius)
64 void operator()(Vector2& current, const PropertyInputContainer& inputs)
66 float spinAngle = inputs[0]->GetFloat();
67 current.x = cos(spinAngle);
68 current.y = sin(spinAngle);
77 * structure of the vertex in the mesh
89 Vertex(const Vector3& position, const Vector3& normal, const Vector2& textureCoord)
92 textureCoord(textureCoord)
97 /************************************************************************************************
98 *** The shader source is used when the MeshActor is not touched***
99 ************************************************************************************************/
101 const char* VERTEX_SHADER_FLAT = DALI_COMPOSE_SHADER(
102 attribute mediump vec3 aPosition;\n
103 attribute mediump vec3 aNormal;\n
104 attribute highp vec2 aTexCoord;\n
105 uniform mediump mat4 uMvpMatrix;\n
106 varying mediump vec2 vTexCoord;\n
109 gl_Position = uMvpMatrix * vec4( aPosition.xy, 0.0, 1.0 );\n
110 vTexCoord = aTexCoord.xy;\n
114 const char* FRAGMENT_SHADER_FLAT = DALI_COMPOSE_SHADER(
115 uniform lowp vec4 uColor;\n
116 uniform sampler2D sTexture;\n
117 varying mediump vec2 vTexCoord;\n
120 gl_FragColor = texture2D( sTexture, vTexCoord ) * uColor;\n
124 /************************************************************
125 ** Custom refraction effect shader***************************
126 ************************************************************/
127 const char* VERTEX_SHADER_REFRACTION = DALI_COMPOSE_SHADER(
128 attribute mediump vec3 aPosition;\n
129 attribute mediump vec3 aNormal;\n
130 attribute highp vec2 aTexCoord;\n
131 uniform mediump mat4 uMvpMatrix;\n
132 varying mediump vec4 vVertex;\n
133 varying mediump vec3 vNormal;\n
134 varying mediump vec2 vTexCoord;\n
135 varying mediump vec2 vTextureOffset;\n
138 gl_Position = uMvpMatrix * vec4( aPosition.xy, 0.0, 1.0 );\n
139 vTexCoord = aTexCoord.xy;\n
142 vVertex = vec4( aPosition, 1.0 );\n
143 float length = max(0.01, length(aNormal.xy)) * 40.0;\n
144 vTextureOffset = aNormal.xy / length;\n
148 const char* FRAGMENT_SHADER_REFRACTION = DALI_COMPOSE_SHADER(
149 precision mediump float;\n
150 uniform mediump float uEffectStrength;\n
151 uniform mediump vec3 uLightPosition;\n
152 uniform mediump vec2 uLightXYOffset;\n
153 uniform mediump vec2 uLightSpinOffset;\n
154 uniform mediump float uLightIntensity;\n
155 uniform lowp vec4 uColor;\n
156 uniform sampler2D sTexture;\n
157 varying mediump vec4 vVertex;\n
158 varying mediump vec3 vNormal;\n
159 varying mediump vec2 vTexCoord;\n
160 varying mediump vec2 vTextureOffset;\n
162 vec3 rgb2hsl(vec3 rgb)\n
164 float epsilon = 1.0e-10;\n
165 vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n
166 vec4 P = mix(vec4(rgb.bg, K.wz), vec4(rgb.gb, K.xy), step(rgb.b, rgb.g));\n
167 vec4 Q = mix(vec4(P.xyw, rgb.r), vec4(rgb.r, P.yzx), step(P.x, rgb.r));\n
171 float chroma = Q.x - min(Q.w, Q.y);\n
172 float hue = abs(Q.z + (Q.w-Q.y) / (6.0*chroma+epsilon));\n
174 float lightness = value - chroma*0.5;\n
175 return vec3( hue, chroma/max( 1.0-abs(lightness*2.0-1.0), 1.0e-1 ), lightness );\n
178 vec3 hsl2rgb( vec3 hsl )\n
181 vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n
182 vec3 p = abs(fract(hsl.xxx + K.xyz) * 6.0 - K.www);\n
183 vec3 RGB = clamp(p - K.xxx, 0.0, 1.0);\n
185 float chroma = ( 1.0 - abs( hsl.z*2.0-1.0 ) ) * hsl.y;\n
186 return ( RGB - 0.5 ) * chroma + hsl.z;\n
191 vec3 normal = normalize( vNormal);\n
193 vec3 lightPosition = uLightPosition + vec3(uLightXYOffset+uLightSpinOffset, 0.0);\n
194 mediump vec3 vecToLight = normalize( (lightPosition - vVertex.xyz) * 0.01 );\n
195 mediump float spotEffect = pow( max(0.05, vecToLight.z ) - 0.05, 8.0);\n
197 spotEffect = spotEffect * uEffectStrength;\n
198 mediump float lightDiffuse = ( ( dot( vecToLight, normal )-0.75 ) *uLightIntensity ) * spotEffect;\n
200 lowp vec4 color = texture2D( sTexture, vTexCoord + vTextureOffset * spotEffect );\n
201 vec3 lightedColor = hsl2rgb( rgb2hsl(color.rgb) + vec3(0.0,0.0,lightDiffuse) );\n
203 gl_FragColor = vec4( lightedColor, color.a ) * uColor;\n
210 /*************************************************/
211 /*Demo using RefractionEffect*****************/
212 /*************************************************/
213 class RefractionEffectExample : public ConnectionTracker
216 RefractionEffectExample(Application& application)
217 : mApplication(application),
227 mLightXYOffsetIndex(Property::INVALID_INDEX),
228 mSpinAngleIndex(Property::INVALID_INDEX),
229 mLightIntensityIndex(Property::INVALID_INDEX),
230 mEffectStrengthIndex(Property::INVALID_INDEX),
231 mChangeTextureButton(),
233 mCurrentTextureId(1),
236 // Connect to the Application's Init signal
237 application.InitSignal().Connect(this, &RefractionEffectExample::Create);
240 ~RefractionEffectExample()
245 // The Init signal is received once (only) during the Application lifetime
246 void Create(Application& application)
248 Window window = application.GetWindow();
249 Vector2 windowSize = window.GetSize();
251 window.KeyEventSignal().Connect(this, &RefractionEffectExample::OnKeyEvent);
253 // Creates a default view with a default tool bar.
254 // The view is added to the window.
255 Toolkit::ToolBar toolBar;
256 Toolkit::Control view;
257 mContent = DemoHelper::CreateView(application,
264 // Add a button to change background. (right of toolbar)
265 mChangeTextureButton = Toolkit::PushButton::New();
266 mChangeTextureButton.SetProperty(Toolkit::Button::Property::UNSELECTED_BACKGROUND_VISUAL, CHANGE_TEXTURE_ICON);
267 mChangeTextureButton.SetProperty(Toolkit::Button::Property::SELECTED_BACKGROUND_VISUAL, CHANGE_TEXTURE_ICON_SELECTED);
268 mChangeTextureButton.ClickedSignal().Connect(this, &RefractionEffectExample::OnChangeTexture);
269 toolBar.AddControl(mChangeTextureButton,
270 DemoHelper::DEFAULT_VIEW_STYLE.mToolBarButtonPercentage,
271 Toolkit::Alignment::HORIZONTAL_RIGHT,
272 DemoHelper::DEFAULT_MODE_SWITCH_PADDING);
273 // Add a button to change mesh pattern. ( left of bar )
274 mChangeMeshButton = Toolkit::PushButton::New();
275 mChangeMeshButton.SetProperty(Toolkit::Button::Property::UNSELECTED_BACKGROUND_VISUAL, CHANGE_MESH_ICON);
276 mChangeMeshButton.SetProperty(Toolkit::Button::Property::SELECTED_BACKGROUND_VISUAL, CHANGE_MESH_ICON_SELECTED);
277 mChangeMeshButton.ClickedSignal().Connect(this, &RefractionEffectExample::OnChangeMesh);
278 toolBar.AddControl(mChangeMeshButton,
279 DemoHelper::DEFAULT_VIEW_STYLE.mToolBarButtonPercentage,
280 Toolkit::Alignment::HORIZONTAL_LEFT,
281 DemoHelper::DEFAULT_MODE_SWITCH_PADDING);
283 // shader used when the screen is not touched, render a flat surface
284 mShaderFlat = Shader::New(VERTEX_SHADER_FLAT, FRAGMENT_SHADER_FLAT);
285 mGeometry = CreateGeometry(MESH_FILES[mCurrentMeshId]);
287 Texture texture = DemoHelper::LoadWindowFillingTexture(window.GetSize(), TEXTURE_IMAGES[mCurrentTextureId]);
288 mTextureSet = TextureSet::New();
289 mTextureSet.SetTexture(0u, texture);
291 mRenderer = Renderer::New(mGeometry, mShaderFlat);
292 mRenderer.SetTextures(mTextureSet);
294 mMeshActor = Actor::New();
295 mMeshActor.AddRenderer(mRenderer);
296 mMeshActor.SetProperty(Actor::Property::SIZE, windowSize);
297 mMeshActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
298 mContent.Add(mMeshActor);
300 // Connect the callback to the touch signal on the mesh actor
301 mContent.TouchedSignal().Connect(this, &RefractionEffectExample::OnTouch);
303 // shader used when the finger is touching the screen. render refraction effect
304 mShaderRefraction = Shader::New(VERTEX_SHADER_REFRACTION, FRAGMENT_SHADER_REFRACTION);
307 mLightXYOffsetIndex = mMeshActor.RegisterProperty("uLightXYOffset", Vector2::ZERO);
309 mLightIntensityIndex = mMeshActor.RegisterProperty("uLightIntensity", 2.5f);
311 mEffectStrengthIndex = mMeshActor.RegisterProperty("uEffectStrength", 0.f);
313 Vector3 lightPosition(-windowSize.x * 0.5f, -windowSize.y * 0.5f, windowSize.x * 0.5f); // top_left
314 mMeshActor.RegisterProperty("uLightPosition", lightPosition);
316 Property::Index lightSpinOffsetIndex = mMeshActor.RegisterProperty("uLightSpinOffset", Vector2::ZERO);
318 mSpinAngleIndex = mMeshActor.RegisterProperty("uSpinAngle", 0.f);
319 Constraint constraint = Constraint::New<Vector2>(mMeshActor, lightSpinOffsetIndex, LightOffsetConstraint(windowSize.x * 0.1f));
320 constraint.AddSource(LocalSource(mSpinAngleIndex));
323 // the animation which spin the light around the finger touch position
324 mLightAnimation = Animation::New(2.f);
325 mLightAnimation.AnimateTo(Property(mMeshActor, mSpinAngleIndex), Math::PI * 2.f);
326 mLightAnimation.SetLooping(true);
327 mLightAnimation.Pause();
330 void SetLightXYOffset(const Vector2& offset)
332 mMeshActor.SetProperty(mLightXYOffsetIndex, offset);
336 * Create a mesh actor with different geometry to replace the current one
338 bool OnChangeMesh(Toolkit::Button button)
340 mCurrentMeshId = (mCurrentMeshId + 1) % NUM_MESH_FILES;
341 mGeometry = CreateGeometry(MESH_FILES[mCurrentMeshId]);
342 mRenderer.SetGeometry(mGeometry);
347 bool OnChangeTexture(Toolkit::Button button)
349 mCurrentTextureId = (mCurrentTextureId + 1) % NUM_TEXTURE_IMAGES;
350 Texture texture = DemoHelper::LoadWindowFillingTexture(mApplication.GetWindow().GetSize(), TEXTURE_IMAGES[mCurrentTextureId]);
351 mTextureSet.SetTexture(0u, texture);
355 bool OnTouch(Actor actor, const TouchEvent& event)
357 switch(event.GetState(0))
359 case PointState::DOWN:
361 mRenderer.SetShader(mShaderRefraction);
363 SetLightXYOffset(event.GetScreenPosition(0));
365 mLightAnimation.Play();
367 if(mStrenghAnimation)
369 mStrenghAnimation.Clear();
372 mStrenghAnimation = Animation::New(0.5f);
373 mStrenghAnimation.AnimateTo(Property(mMeshActor, mEffectStrengthIndex), 1.f);
374 mStrenghAnimation.Play();
378 case PointState::MOTION:
380 // make the light position following the finger movement
381 SetLightXYOffset(event.GetScreenPosition(0));
385 case PointState::LEAVE:
386 case PointState::INTERRUPTED:
388 mLightAnimation.Pause();
390 if(mStrenghAnimation)
392 mStrenghAnimation.Clear();
394 mStrenghAnimation = Animation::New(0.5f);
395 mStrenghAnimation.AnimateTo(Property(mMeshActor, mEffectStrengthIndex), 0.f);
396 mStrenghAnimation.FinishedSignal().Connect(this, &RefractionEffectExample::OnTouchFinished);
397 mStrenghAnimation.Play();
400 case PointState::STATIONARY:
409 void OnTouchFinished(Animation& source)
411 mRenderer.SetShader(mShaderFlat);
412 SetLightXYOffset(Vector2::ZERO);
415 Geometry CreateGeometry(const std::string& objFileName)
417 std::vector<Vector3> vertexPositions;
418 Vector<unsigned int> faceIndices;
419 Vector<float> boundingBox;
420 // read the vertice and faces from the .obj file, and record the bounding box
421 ReadObjFile(objFileName, boundingBox, vertexPositions, faceIndices);
423 std::vector<Vector2> textureCoordinates;
424 // align the mesh, scale it to fit the screen size, and calculate the texture coordinate for each vertex
425 ShapeResizeAndTexureCoordinateCalculation(boundingBox, vertexPositions, textureCoordinates);
427 // re-organize the mesh, the vertices are duplicated, each vertex only belongs to one triangle.
428 // Without sharing vertex between triangle, so we can manipulate the texture offset on each triangle conveniently.
429 std::vector<Vertex> vertices;
431 std::size_t size = faceIndices.Size();
432 vertices.reserve(size);
434 for(std::size_t i = 0; i < size; i = i + 3)
436 Vector3 edge1 = vertexPositions[faceIndices[i + 2]] - vertexPositions[faceIndices[i]];
437 Vector3 edge2 = vertexPositions[faceIndices[i + 1]] - vertexPositions[faceIndices[i]];
438 Vector3 normal = edge1.Cross(edge2);
441 // make sure all the faces are front-facing
444 vertices.push_back(Vertex(vertexPositions[faceIndices[i]], normal, textureCoordinates[faceIndices[i]]));
445 vertices.push_back(Vertex(vertexPositions[faceIndices[i + 1]], normal, textureCoordinates[faceIndices[i + 1]]));
446 vertices.push_back(Vertex(vertexPositions[faceIndices[i + 2]], normal, textureCoordinates[faceIndices[i + 2]]));
451 vertices.push_back(Vertex(vertexPositions[faceIndices[i]], normal, textureCoordinates[faceIndices[i]]));
452 vertices.push_back(Vertex(vertexPositions[faceIndices[i + 2]], normal, textureCoordinates[faceIndices[i + 2]]));
453 vertices.push_back(Vertex(vertexPositions[faceIndices[i + 1]], normal, textureCoordinates[faceIndices[i + 1]]));
457 Property::Map vertexFormat;
458 vertexFormat["aPosition"] = Property::VECTOR3;
459 vertexFormat["aNormal"] = Property::VECTOR3;
460 vertexFormat["aTexCoord"] = Property::VECTOR2;
461 VertexBuffer surfaceVertices = VertexBuffer::New(vertexFormat);
462 surfaceVertices.SetData(&vertices[0], vertices.size());
464 Geometry surface = Geometry::New();
465 surface.AddVertexBuffer(surfaceVertices);
470 void ReadObjFile(const std::string& objFileName,
471 Vector<float>& boundingBox,
472 std::vector<Vector3>& vertexPositions,
473 Vector<unsigned int>& faceIndices)
475 std::streampos bufferSize = 0;
476 Dali::Vector<char> fileBuffer;
477 if(!Dali::FileLoader::ReadFile(objFileName, bufferSize, fileBuffer, Dali::FileLoader::FileType::TEXT))
479 DALI_LOG_WARNING("file open failed for: \"%s\"", objFileName.c_str());
483 fileBuffer.PushBack('\0');
485 std::stringstream iss(&fileBuffer[0], std::ios::in);
487 boundingBox.Resize(6);
488 boundingBox[0] = boundingBox[2] = boundingBox[4] = std::numeric_limits<float>::max();
489 boundingBox[1] = boundingBox[3] = boundingBox[5] = -std::numeric_limits<float>::max();
492 while(std::getline(iss, line))
494 if(line[0] == 'v' && std::isspace(line[1])) // vertex
496 std::istringstream iss(line.substr(2), std::istringstream::in);
499 while(iss >> vertex[i++] && i < 3)
501 if(vertex.x < boundingBox[0]) boundingBox[0] = vertex.x;
502 if(vertex.x > boundingBox[1]) boundingBox[1] = vertex.x;
503 if(vertex.y < boundingBox[2]) boundingBox[2] = vertex.y;
504 if(vertex.y > boundingBox[3]) boundingBox[3] = vertex.y;
505 if(vertex.z < boundingBox[4]) boundingBox[4] = vertex.z;
506 if(vertex.z > boundingBox[5]) boundingBox[5] = vertex.z;
507 vertexPositions.push_back(vertex);
509 else if(line[0] == 'f') //face
511 unsigned int numOfInt = 3;
514 std::size_t found = line.find('/');
515 if(found == std::string::npos)
523 std::istringstream iss(line.substr(2), std::istringstream::in);
524 Dali::Vector<unsigned int> indices;
525 indices.Resize(numOfInt);
527 while(iss >> indices[i++] && i < numOfInt)
529 unsigned int step = (i + 1) / 3;
530 faceIndices.PushBack(indices[0] - 1);
531 faceIndices.PushBack(indices[step] - 1);
532 faceIndices.PushBack(indices[2 * step] - 1);
537 void ShapeResizeAndTexureCoordinateCalculation(const Vector<float>& boundingBox,
538 std::vector<Vector3>& vertexPositions,
539 std::vector<Vector2>& textureCoordinates)
541 Vector3 bBoxSize(boundingBox[1] - boundingBox[0], boundingBox[3] - boundingBox[2], boundingBox[5] - boundingBox[4]);
542 Vector3 bBoxMinCorner(boundingBox[0], boundingBox[2], boundingBox[4]);
544 Vector2 windowSize = mApplication.GetWindow().GetSize();
545 Vector3 scale(windowSize.x / bBoxSize.x, windowSize.y / bBoxSize.y, 1.f);
546 scale.z = (scale.x + scale.y) / 2.f;
548 textureCoordinates.reserve(vertexPositions.size());
550 for(std::vector<Vector3>::iterator iter = vertexPositions.begin(); iter != vertexPositions.end(); iter++)
552 Vector3 newPosition((*iter) - bBoxMinCorner);
554 textureCoordinates.push_back(Vector2(newPosition.x / bBoxSize.x, newPosition.y / bBoxSize.y));
556 newPosition -= bBoxSize * 0.5f;
557 (*iter) = newPosition * scale;
562 * Main key event handler
564 void OnKeyEvent(const KeyEvent& event)
566 if(event.GetState() == KeyEvent::DOWN)
568 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
576 Application& mApplication;
578 TextureSet mTextureSet;
584 Shader mShaderRefraction;
586 Animation mLightAnimation;
587 Animation mStrenghAnimation;
589 Property::Index mLightXYOffsetIndex;
590 Property::Index mSpinAngleIndex;
591 Property::Index mLightIntensityIndex;
592 Property::Index mEffectStrengthIndex;
594 Toolkit::PushButton mChangeTextureButton;
595 Toolkit::PushButton mChangeMeshButton;
596 unsigned int mCurrentTextureId;
597 unsigned int mCurrentMeshId;
600 /*****************************************************************************/
602 int DALI_EXPORT_API main(int argc, char** argv)
604 Application app = Application::New(&argc, &argv, DEMO_THEME_PATH);
605 RefractionEffectExample theApp(app);