2 * Copyright (c) 2015 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.
18 #include <dali-toolkit/internal/controls/page-turn-view/page-turn-effect.h>
19 #include <dali/public-api/math/matrix.h>
22 using namespace Dali::Toolkit;
24 void CommonParametersConstraint( Dali::Matrix& current, const PropertyInputContainer& inputs )
26 const Vector2& originalCenter = inputs[0]->GetVector2();
27 Vector2 currentCenter = inputs[1]->GetVector2();
28 const Vector2& pageSize = inputs[2]->GetVector2();
30 // calculate the curve direction and the vanishing point
31 // here, the vanishing point is the intersection of spine with the line passing through original center and vertical to curve direction
32 Vector2 curveDirection( currentCenter - originalCenter );
33 curveDirection.Normalize();
34 if( fabs(curveDirection.y) < 0.01f) // eliminate the possibility of division by zero in the next step
36 curveDirection.y = 0.01f;
38 float vanishingPointY = originalCenter.y + curveDirection.x * originalCenter.x / curveDirection.y;
40 float curveEndY, cosTheta ,sinTheta ,translateX, translateY;
41 // when the vanishing point is very far away, make it infinitely, in this case, the page bent horizontally
42 const float THRESHOLD(20.0);
43 if( fabs(vanishingPointY-pageSize.y*0.5f) >= pageSize.y*THRESHOLD )
45 curveDirection = Vector2(-1.f,0.f);
46 currentCenter.y = originalCenter.y;
48 curveEndY = originalCenter.y;
51 translateX = currentCenter.x - originalCenter.x;
52 translateY = vanishingPointY;
56 curveEndY = currentCenter.y - curveDirection.y * (currentCenter.x/curveDirection.x) ;
57 Vector2 v1( currentCenter.x, currentCenter.y - vanishingPointY );
59 Vector2 v2( originalCenter.x, originalCenter.y - vanishingPointY );
61 cosTheta = v1.x*v2.x + v1.y*v2.y;
62 sinTheta = ( vanishingPointY > pageSize.y*0.5f ) ? sqrt(1.0-cosTheta*cosTheta) : -sqrt(1.0-cosTheta*cosTheta);
63 translateX = currentCenter.x - cosTheta*originalCenter.x - sinTheta*( originalCenter.y-vanishingPointY );
64 translateY = currentCenter.y + sinTheta*originalCenter.x - cosTheta*( originalCenter.y-vanishingPointY );
67 float originalLength = fabs(originalCenter.x/curveDirection.x);
68 float currentLength = fabs(currentCenter.x/curveDirection.x);
69 float curveHeight = 0.45f*sqrt(originalLength*originalLength - currentLength*currentLength);
71 float* parameterArray = current.AsFloat();
72 parameterArray[0] = cosTheta;
73 parameterArray[1] = -sinTheta;
74 parameterArray[2] = originalCenter.x;
75 parameterArray[3] = originalCenter.y;
76 parameterArray[4] = sinTheta;
77 parameterArray[5] = cosTheta;
78 parameterArray[6] = currentCenter.x;
79 parameterArray[7] = currentCenter.y;
80 parameterArray[8] = translateX;
81 parameterArray[9] = translateY;
82 parameterArray[10] = vanishingPointY;
83 parameterArray[11] = curveEndY;
84 parameterArray[12] = curveDirection.x;
85 parameterArray[13] = curveDirection.y;
86 parameterArray[14] = curveHeight;
87 parameterArray[15] = currentLength;
90 void Dali::Toolkit::Internal::PageTurnApplyInternalConstraint( ShaderEffect& shaderEffect)
92 Constraint constraint = Constraint::New<Dali::Matrix>( shaderEffect, shaderEffect.GetPropertyIndex( "uCommonParameters" ), CommonParametersConstraint );
93 constraint.AddSource( LocalSource( shaderEffect.GetPropertyIndex( "uOriginalCenter" ) ) );
94 constraint.AddSource( LocalSource( shaderEffect.GetPropertyIndex( "uCurrentCenter" ) ) );
95 constraint.AddSource( LocalSource( shaderEffect.GetPropertyIndex( "uPageSize" ) ) );
99 ShaderEffect Dali::Toolkit::Internal::CreatePageTurnEffect(bool enableBlending)
101 std::string vertexShader = DALI_COMPOSE_SHADER(
103 * The common parameters for all the vertices, calculate in CPU then pass into the shader as uniforms
105 * first part of the page, (outside the the line passing through original center and vertical to curve direction)
106 * no Z change, only 2D rotation and translation
107 * ([0][0],[0][1],[1][0],[1][1]) mat2 rotateMatrix
108 * ([2][0],[2][1]) vec2 translationVector
110 * ([0][2],[0][3]) vec2 originalCenter: Typically the press down position of the Pan Gesture
111 * ([1][2],[1][3]) vec2 currentCenter: Typically the current position of the Pan Gesture
112 * ([3][0],[3][1]) vec2 curveDirection: The normalized vector pointing from original center to current center
113 * ([2][2]) float vanishingPointY: The Y coordinate of the intersection of the spine
114 * and the line which goes through the original center and is vertical to the curveDirection
115 * ([2][3]) float curveEndY: The Y coordinate of intersection of the spine and the line through both original and current center
116 * ([3][2]) float curveHeight: The height of the interpolated hermite curve.
117 * ([3][3]) float currentLength: The length from the current center to the curveEnd.
119 precision mediump float;\n
120 uniform mat4 uCommonParameters;\n
122 uniform vec2 uPageSize;\n
123 uniform float uIsTurningBack;\n
124 uniform float uShadowWidth;\n
125 varying vec3 vNormal;\n
126 varying vec4 vPosition;\n
127 varying float vEdgeShadow;\n
131 vec4 position = vec4( aPosition.xy, 0.0, 1.0);\n
132 vec2 currentCenter = vec2( uCommonParameters[1][2], uCommonParameters[1][3]);\n
133 vec2 originalCenter = vec2( uCommonParameters[0][2], uCommonParameters[0][3]);\n
134 vec3 normal = vec3(0.0,0.0,1.0);\n
136 if(currentCenter.x < originalCenter.x)\n
138 // change the coordinate origin from the center of the page to its top-left
139 position.xy += uPageSize * 0.5;\n
140 vec2 curveDirection = vec2( uCommonParameters[3]);\n
141 vec3 vanishingPoint = vec3(0.0, uCommonParameters[2][2], 0.0);\n
142 // first part of the page, (outside the the line passing through original center and vertical to curve direction)
143 //no Z change, only 2D rotation and translation
144 if( dot(curveDirection, position.xy - originalCenter) < 0.0 )
146 position.y -= vanishingPoint.y;\n
147 position.xy = mat2(uCommonParameters)*position.xy + vec2( uCommonParameters[2]);\n
149 // second part of the page, bent as a ruled surface
152 // calculate on the flat plane, between
153 // the first line passing through current vertex and vanishing point
154 // the second line passing through original center and current center
155 vec2 curveEnd = vec2( 0.0, uCommonParameters[2][3] );\n
156 vec2 curFlatDirection = vec2(0.0,1.0);\n
157 float lengthFromCurve = position.y - originalCenter.y;\n
158 float lengthOnCurve = position.x;\n
159 if(currentCenter.y != originalCenter.y)\n
161 curFlatDirection = normalize(position.xy - vanishingPoint.xy);\n
162 lengthFromCurve = (curveEnd.x*curveDirection.y-curveEnd.y*curveDirection.x-position.x*curveDirection.y+position.y*curveDirection.x)
163 / (curFlatDirection.x*curveDirection.y-curFlatDirection.y*curveDirection.x);\n
164 lengthOnCurve = length(position.xy+lengthFromCurve*curFlatDirection-curveEnd);\n
167 // define the control points of hermite curve, composed with two segments
168 // calulation is carried out on the 2D plane which is passing through both current and original center and vertical to the image plane
169 float currentLength = uCommonParameters[3][3];\n
170 float originalLength = abs(originalCenter.x/curveDirection.x);\n
171 float height = uCommonParameters[3][2];\n
172 float percentage = currentLength/originalLength;\n
173 //vec2 SegmentOneControlPoint0 = vec2(0.0, 0.0);
174 vec2 SegmentOneControlPoint1 = vec2((0.65*percentage - 0.15)*originalLength, (0.8 + 0.2 * percentage)*height); \n
175 vec2 SegmentTwoControlPoint0 = SegmentOneControlPoint1;\n
176 vec2 SegmentTwoControlPoint1 = vec2(currentLength, 0.0); \n
177 vec2 SegmentOneTangentVector0 = SegmentOneControlPoint1;\n
178 vec2 SegmentOneTangentVector1 = vec2(0.5*originalLength,0.0);\n
179 vec2 SegmentTwoTangentVector0 = SegmentOneTangentVector1;\n
180 vec2 SegmentTwoTangentVector1 = SegmentOneTangentVector1;\n
182 // calulate the corresponding curve point position and its tangent vector
183 // it is a linear mapping onto nonlinear curves, might cause some unwanted deformation
184 // but as there are no analytical method to calculate the curve length on arbitrary segment
185 // no efficient way to solve this nonlinear mapping, Numerical approximation would cost too much computation in shader
188 float t0 = lengthOnCurve / originalLength;\n
194 curvePoint2D = (-2.0*t_3+3.0*t_2)*SegmentOneControlPoint1
195 + (t_3-2.0*t_2+t)*SegmentOneTangentVector0 + (t_3-t_2)*SegmentOneTangentVector1;\n
196 tangent = (-6.0*t_2+6.0*t)*SegmentOneControlPoint1
197 + (3.0*t_2-4.0*t+1.0)*SegmentOneTangentVector0 + (3.0*t_2-2.0*t)*SegmentOneTangentVector1;\n
201 float t = 2.0*t0-1.0;\n
204 curvePoint2D = (2.0*t_3-3.0*t_2+1.0)*SegmentTwoControlPoint0 + (-2.0*t_3+3.0*t_2)*SegmentTwoControlPoint1
205 + (t_3-2.0*t_2+t)*SegmentTwoTangentVector0 + (t_3-t_2)*SegmentTwoTangentVector1;\n
206 tangent = (6.0*t_2-6.0*t)*SegmentTwoControlPoint0 + (-6.0*t_2+6.0*t)*SegmentTwoControlPoint1
207 + (3.0*t_2-4.0*t+1.0)*SegmentTwoTangentVector0 + (3.0*t_2-2.0*t)*SegmentTwoTangentVector1;\n
208 // a trick to eliminate some optical illusion caused by the gradient matter of normal in per-fragment shading
209 // which is caused by linear interpolation of normal vs. nonlinear lighting
210 // will notice some artifact in the areas with dramatically normal changes, so compress the normal differences here
211 tangent.y *= min(1.0, length(position.xyz - vanishingPoint) / uPageSize.y ); \n
213 vec3 curvePoint = vec3(curveEnd - curvePoint2D.x*curveDirection,max(0.0,curvePoint2D.y));\n
214 vec3 tangentVector = vec3(-tangent.x*curveDirection,tangent.y);\n
216 // locate the new vertex position on the line passing through both vanishing point and the calculated curve point position
217 vec3 curLiftDirection = vec3(0.0,-1.0,0.0);\n
218 if(currentCenter.y != originalCenter.y)\n
220 curLiftDirection = normalize(curvePoint - vanishingPoint);\n
221 tangentVector *= (curveDirection.y > 0.0) ? -1.0 : 1.0;\n
222 // an heuristic adjustment here, to compensate the linear parameter mapping onto the nonlinear curve
223 float Y0 = position.y - curveDirection.y * (position.x/curveDirection.x); \n
226 if(abs(Y0-vanishingPoint.y) > abs(curveEnd.y-vanishingPoint.y)) \n
228 proportion = abs(curveEnd.y - Y0) / (abs(curveEnd.y-Y0)+abs(curveEnd.y - vanishingPoint.y)); \n
229 refLength = proportion*length(originalCenter-vanishingPoint.xy) / (proportion-1.0); \n
233 proportion = abs(curveEnd.y - Y0) / abs(curveEnd.y - vanishingPoint.y);\n
234 refLength = proportion*length(originalCenter-vanishingPoint.xy); \n
236 float Y1 = currentCenter.y - (normalize(currentCenter-vanishingPoint.xy)).y * refLength; \n
237 position.y = mix(Y0, Y1, t0); \n
239 position.xz = curvePoint.xz - lengthFromCurve*curLiftDirection.xz;\n
240 // calculate the normal vector, will be used for lighting
241 normal = cross(curLiftDirection, normalize(tangentVector));\n
242 // the signature of Z is decided by the page turning direction:
243 // from left to right(negative); from right to left (positive)
244 position.z *= -uIsTurningBack;\n
245 normal.xy *= -uIsTurningBack;\n
247 // change the coordinate origin from the top-left of the page to its center
248 position.xy -= uPageSize * 0.5; \n
250 position.z += aPosition.z;\n
251 gl_Position = uMvpMatrix * position;\n
252 // varying parameters for fragment shader
253 vTexCoord = aTexCoord;
254 vNormal = uNormalMatrix*normal;\n
255 vPosition = uModelView * position;\n
258 std::string vertexShaderWithFakedShadow = DALI_COMPOSE_SHADER(
259 // display shadow, the fake shadow value is calculated according to the height and the distance from page edge
260 vTexCoord.x = (aTexCoord.x-sTextureRect.s) /( 1.0 - uShadowWidth ) + sTextureRect.s;\n
261 vTexCoord.y = ( aTexCoord.y-sTextureRect.t-0.5*uShadowWidth*(sTextureRect.q-sTextureRect.t) )/( 1.0 - uShadowWidth ) + sTextureRect.t;\n
262 float heightCoef = (1.0 + position.z*uIsTurningBack*3.0 / uPageSize.x) * 0.6;
263 vEdgeShadow = clamp(0.9 - heightCoef, 0.0, 0.9 ); \n
264 if( vTexCoord.y >= sTextureRect.q || vTexCoord.y <= sTextureRect.t || vTexCoord.x >= sTextureRect.p )\n
266 float inversedShadowWidth = (1.0-uShadowWidth) / uShadowWidth ;\n
267 float alpha1 = (vTexCoord.x-sTextureRect.p) * inversedShadowWidth / (sTextureRect.p - sTextureRect.s);\n
268 inversedShadowWidth = 2.0 * inversedShadowWidth / (sTextureRect.q - sTextureRect.t); \n
269 float alpha2 = (vTexCoord.y-sTextureRect.q) * inversedShadowWidth;\n
270 float alpha3 = (sTextureRect.t-vTexCoord.y) * inversedShadowWidth;\n
272 if(alpha1 > 0.0 && alpha2 > 0.0) alpha = sqrt(alpha2*alpha2+alpha1*alpha1)/sqrt(1.0 + max(alpha1,alpha2)*max(alpha1,alpha2));\n //bottom-right corner
273 else if(alpha1 > 0.0 && alpha3 > 0.0) alpha = sqrt(alpha3*alpha3+alpha1*alpha1)/sqrt(1.0+max(alpha1,alpha3)*max(alpha1,alpha3));\n //top-right corner
274 else alpha = max(alpha1,max(alpha2,alpha3)); \n
275 alpha = 0.9 - alpha*0.9;\n
276 vEdgeShadow = clamp(alpha - heightCoef, 0.0, 0.9 ); \n
280 std::string vertexShaderEnd("}");
282 std::string fragmentShaderPartOne = DALI_COMPOSE_SHADER(
283 precision mediump float;\n
284 uniform vec2 uPageSize;\n
285 uniform vec2 uSpineShadowParameter;\n
286 varying vec3 vNormal;\n
287 varying vec4 vPosition;\n
288 varying float vEdgeShadow;\n
292 // need to re-normalize the interpolated normal
293 vec3 normal = normalize(vNormal);\n
295 float spineShadowCoef = 1.0; \n
298 std::string fragmentShaderWithFakedShadow = DALI_COMPOSE_SHADER(
299 if( vTexCoord.y > sTextureRect.q || vTexCoord.y < sTextureRect.t || vTexCoord.x > sTextureRect.p )\n
300 texel = vec4(0.0,0.0,0.0,vEdgeShadow);
304 std::string fragmentShaderPartTwo = DALI_COMPOSE_SHADER(
306 // display page content
307 // display back image of the page, flip the texture
308 if( dot(vPosition.xyz, normal) > 0.0 ) texel = texture2D( sTexture, vec2( sTextureRect.p+sTextureRect.s-vTexCoord.x, vTexCoord.y ) );\n
309 // display front image of the page
310 else texel = texture2D( sTexture, vTexCoord );\n
311 // display book spine, a stripe of shadowed texture
312 float pixelPos = (vTexCoord.x-sTextureRect.s)*uPageSize.x; \n
313 if(pixelPos < uSpineShadowParameter.x) \n
315 float x = pixelPos - uSpineShadowParameter.x;\n
316 float y = sqrt( uSpineShadowParameter.x*uSpineShadowParameter.x - x*x);\n
317 spineShadowCoef = normalize( vec2( uSpineShadowParameter.y*x/uSpineShadowParameter.x, y ) ).y;\n
320 // calculate the lighting
321 // set the ambient color as vec3(0.4);
322 float lightColor = abs( normal.z ) * 0.6 + 0.4;\n
323 gl_FragColor = vec4( ( spineShadowCoef* lightColor)* texel.rgb , texel.a ) * uColor;\n
327 // Create the implementation, temporarily owned on stack,
328 Dali::ShaderEffect shaderEffectCustom;
329 std::ostringstream vertexShaderStringStream;
330 std::ostringstream fragmentShaderStringStream;
333 vertexShaderStringStream<< vertexShader << vertexShaderWithFakedShadow << vertexShaderEnd;
334 fragmentShaderStringStream<< fragmentShaderPartOne << fragmentShaderWithFakedShadow << fragmentShaderPartTwo;
335 shaderEffectCustom = Dali::ShaderEffect::New( vertexShaderStringStream.str(), fragmentShaderStringStream.str(),
336 ShaderEffect::GeometryHints( ShaderEffect::HINT_GRID | ShaderEffect::HINT_DEPTH_BUFFER | ShaderEffect::HINT_BLENDING) );
340 vertexShaderStringStream<< vertexShader << vertexShaderEnd;
341 fragmentShaderStringStream<< fragmentShaderPartOne << fragmentShaderPartTwo;
342 shaderEffectCustom = Dali::ShaderEffect::New( vertexShaderStringStream.str(), fragmentShaderStringStream.str(),
343 ShaderEffect::GeometryHints( ShaderEffect::HINT_GRID | ShaderEffect::HINT_DEPTH_BUFFER ) );
346 static const float DEFAULT_SHADOW_WIDTH(0.15f);
347 static const Vector2 DEFAULT_SPINE_SHADOW_PARAMETER(50.0f, 20.0f);
349 Vector2 defaultPageSize = Dali::Stage::GetCurrent().GetSize();
350 Dali::Matrix zeroMatrix(true);
351 shaderEffectCustom.SetUniform( "uCommonParameters", zeroMatrix );
352 shaderEffectCustom.SetUniform( "uPageSize", defaultPageSize/(1.f-DEFAULT_SHADOW_WIDTH) );
353 shaderEffectCustom.SetUniform( "uShadowWidth", DEFAULT_SHADOW_WIDTH );
354 shaderEffectCustom.SetUniform( "uSpineShadowParameter", DEFAULT_SPINE_SHADOW_PARAMETER );
356 shaderEffectCustom.RegisterProperty( "uOriginalCenter", Vector2( defaultPageSize[0], defaultPageSize[1]*0.5f ) );
357 shaderEffectCustom.RegisterProperty( "uCurrentCenter", Vector2( defaultPageSize[0], defaultPageSize[1]*0.5f ) );
359 PageTurnApplyInternalConstraint(shaderEffectCustom);
361 // setting isTurningBack to -1.0f here means turning page forward
362 shaderEffectCustom.SetUniform( "uIsTurningBack", -1.0f );
364 return shaderEffectCustom;