书接上文,间隔达到翻页动画自身的可用,还需求增加对翻页规模的约束,例如下图这种状况:
那么这次就来解决这个问题,首先还是奉上这次完成后的作用:
计划设计
在通过canvas来完成的计划中,完成一个对规模的处理并不是一件很困难的事,canvas生成内容基本是跟path或多或少挂钩的,因而我们可以直接修正Path的规模来处理规模约束问题。
而在Fragment Shader中,并没有线条和路径的概念,片段着色器仅仅会依据程序代码,将像素点对应的纹路信息生成出来,其操作的目标不是path,而是一个个的像素点,关于某个点来说,它不会像path一样能得知其他点的方位,更无从获悉其他点是否在展示规模外。因而,关于首先要做的事,便是寻找一种全新的规模判别和批改计划。
吐槽完,回到主线使命中来,中心的心酸泪就不提了,直接说现在的计划:
假如依照翻页角规模最大来设想,那么翻页部分的页脚,其轨道等同于下图的绿色轨道:
其实便是环绕左上角,以书页横向宽度为半径的一个圆而已;
如果在这个绿圈规模内,那么翻页规模怎么样都不会在规模外,在绿圈规模外,则就会导致翻页规模的溢出,需求处理。这样,怎么判别是否需求调整方位的判别依据就有了,依据当时接触点是否在绿圈规模内即可。
有了判别依据,第二步便是怎么批改,这点我是拟定了这样的计划:
- 设定翻页角对应x=0的点偏移60度的直线为基准线,如果接触点方位坐落这个基准线上,那么将接触点改为半径aspect的那个弧线跟60度直线的交点,这个倒直接这么处理即可;
- 如果不在上述60度的直线上,那么取当时接触点,跟基准线的间隔,与半径aspect的弧度的间隔,这两者取较小值
- 获取到这个数值后,以接触点对应在弧线上的映射点为基准,以获取到的数值为弧长进行弧度偏移,方向取接触点坐落基准线的上下来决定。
嗯,我知道这玩意一般是看不懂的……即使是我,年前写下的注释,当年后在写这篇文章的时,回忆这个注释也愣是没太看懂
为了避免产生以上图片的状况,这次就对这些过程进行拆解和详细阐明
首先首要还是对图片中的一些要害点进行标示,以上图的为例:
那么依据这些标示点,进行过程拆解:
- 核算翻页对应点
vec2 startPoint = vec2(0.0, cornerFrom.y==0.0?0.0:1.0);
- 核算当时接触点在基准线上的投影点方位
vec2 vector = normalize(vec2(0.5, 0.5*tan(pi/3)));/// 归一化基准线向量
vec2 targetMouse = mouse.xy;/// 接触点
vec2 v = targetMouse - startPoint; /// 接触点跟翻页对应点的向量
float proj_length = dot(v, vector); /// 核算投影长度
vec2 targetMouse_proj = startPoint + proj_length*vector; ///起点+向量*长度便是具体方位点
- 依据投影点算出接触点到基准线的间隔
float base_line_distance = length(targetMouse_proj - targetMouse);
- 当时接触点到弧线的间隔(也便是当时接触点到翻页对应点-弧线半径),并取两者最小值做为成果
float arc_distance = distance(targetMouse, startPoint) - aspect;
float actual_distance = min(abs(base_line_distance), abs(arc_distance));
- 核算当时接触点在弧线上的投影点
vec2 currentMouse_arc_proj = startPoint+ normalize(mouse - startPoint)*aspect;
- 从这个投影点的方位,依据当时接触点相关于基准线的上下来决定方向,偏移第4步成果的弧线长度。
vec2 newPoint_arc_proj = pointOnCircle(startPoint, currentMouse_arc_proj, aspect, actual_distance/2, mouse.y<=tan(pi/3)*mouse.x);
- 依据第六步批改后的方位,修正最终接触点方位
mouse = newPoint_arc_proj;
currentMouse.xy = mouse * resolution.xy / vec2(aspect, 1.0);
之后的流程就依照本来的过程来便是了,关于后续过程来说,他们核算的接触点已经是批改好的了。
shader文件代码
#include <flutter/runtime_effect.glsl>
uniform vec2 resolution;
uniform vec4 iMouse;
uniform sampler2D image;
#define pi 3.14159265359
#define radius 0.05
#define shadowWidth 0.02
#define TRANSPARENT vec4(0.0, 0.0, 0.0, 0.0)
out vec4 fragColor;
float calShadow(vec2 targetPoint, float aspect){
if (targetPoint.y>=1.0){
return max(pow(clamp((targetPoint.y-1.0)/shadowWidth, 0.0, 0.9), 0.2), pow(clamp((targetPoint.x-aspect)/shadowWidth, 0.0, 0.9), 0.2));
} else {
return max(pow(clamp((0.0-targetPoint.y)/shadowWidth, 0.0, 0.9), 0.2), pow(clamp((targetPoint.x-aspect)/shadowWidth, 0.0, 0.9), 0.2));
}
}
vec2 rotate(vec2 v, float a) {
float s = sin(a);
float c = cos(a);
return vec2(c * v.x - s * v.y, s * v.x + c * v.y);
}
vec2 pointOnCircle(vec2 center, vec2 startPoint, float currentRadius, float arcLength, bool clockwise) {
float theta = arcLength / currentRadius;
vec2 startVec = startPoint - center;
startVec = normalize(startVec);
float rotationAngle = clockwise ? -theta : theta;
vec2 rotatedVec = rotate(startVec, rotationAngle);
vec2 endPoint = center + rotatedVec * currentRadius;
return endPoint;
}
void main() {
vec2 fragCoord = FlutterFragCoord().xy;
float aspect = resolution.x / resolution.y;
vec2 uv = fragCoord * vec2(aspect, 1.0) / resolution.xy;
vec4 currentMouse = iMouse;
vec2 cornerFrom = (currentMouse.w<resolution.y/2)?vec2(aspect, 0.0):vec2(aspect, 1.0);
// 归一化鼠标坐标
vec2 mouse = currentMouse.xy * vec2(aspect, 1.0) / resolution.xy;
vec2 testPoint = vec2(0.25, 0.5);
// 鼠标方位跟左上角的间隔大于aspect,才会产生翻页规模大于屏幕
if (distance(mouse.xy, vec2(0.0, cornerFrom.y))>(aspect)){
// 修复规则,结合两个部分:
// 1. 如果接触点方位坐落左上角向右下角60度的直线上,那么将接触点改为半径aspect的那个弧线跟60度直线的交点,这个倒直接这么处理即可;
// 2. 如果不在上述60度的直线上,那么取当时接触点,跟60度直线的间隔,与半径aspect的弧度的间隔,这两者取较小值
// 3. 获取到第二步的数值后,以当时接触点跟左上角的直线,与半径aspect的弧线的交点为基准,依据获取到的值进行一定的偏移,偏移按弧线的方向进行,弧线长度等于偏移值?
vec2 startPoint = vec2(0.0, cornerFrom.y==0.0?0.0:1.0);
vec2 vector = normalize(vec2(0.5, 0.5*tan(pi/3)));
vec2 targetMouse = mouse.xy;
vec2 v = targetMouse - startPoint;
float proj_length = dot(v, vector);
vec2 targetMouse_proj = startPoint + proj_length*vector;
/// 间隔基准直线的间隔
float base_line_distance = length(targetMouse_proj - targetMouse);
/// 当时接触点间隔弧线间隔
float arc_distance = distance(targetMouse, startPoint) - aspect;
// 取小值
float actual_distance = min(abs(base_line_distance), abs(arc_distance));
// 当时接触点对应在弧线上的映射点
vec2 currentMouse_arc_proj = startPoint+ normalize(mouse - startPoint)*aspect;
vec2 newPoint_arc_proj = pointOnCircle(startPoint, currentMouse_arc_proj, aspect, actual_distance/2, mouse.y<=tan(pi/3)*mouse.x);
// 依据最新核算成果,批改鼠标参数
mouse = newPoint_arc_proj;
currentMouse.xy = mouse * resolution.xy / vec2(aspect, 1.0);
}
// 鼠标方向的向量
vec2 mouseDir = normalize(abs(cornerFrom* resolution.xy / vec2(aspect, 1.0)) - currentMouse.xy);
// 翻页辅助核算点起点
vec2 origin = clamp(mouse - mouseDir * mouse.x / mouseDir.x, 0.0, 1.0);
// 鼠标辅助核算间隔
float mouseDist = distance(mouse, origin);
if (mouseDir.x < 0.0) {
mouseDist = distance(mouse, origin);
}
float proj = dot(uv - origin, mouseDir);
float dist = proj - (mouse.x<0?-mouseDist:mouseDist);
vec2 curlAxisLinePoint = uv - dist * mouseDir;
// 让翻页页脚能跟随接触点
float actualDist = distance(mouse, cornerFrom);
if (actualDist>=pi*radius) {
float params = (actualDist-pi*radius)/2;
curlAxisLinePoint += params * mouseDir;
dist -=params;
}
if (dist > radius) {
fragColor = vec4(0.0, 0.0, 0.0, (1.0 - pow(clamp((dist - radius)*pi, 0.0, 1.0), 0.2)));
} else if (dist >= 0.0) {
// map to cylinder point
float theta = asin(dist / radius);
vec2 p2 = curlAxisLinePoint + mouseDir * (pi - theta) * radius;
vec2 p1 = curlAxisLinePoint + mouseDir * theta * radius;
if (p2.x <= aspect && p2.y <= 1.0 && p2.x > 0.0 && p2.y > 0.0){
uv = p2;
fragColor = texture(image, uv * vec2(1.0 / aspect, 1.0));
fragColor.rgb =mix(fragColor.rgb, vec3(1.0), 0.25);
fragColor.rgb *= pow(clamp((radius - dist) / radius, 0.0, 1.0), 0.2);
} else {
uv = p1;
fragColor = texture(image, uv * vec2(1.0 / aspect, 1.0));
if (p2.x <= aspect+shadowWidth && p2.y <= 1.0+shadowWidth&& p2.x > 0.0-shadowWidth && p2.y > 0.0-shadowWidth){
float shadow = calShadow(p2, aspect);
fragColor = vec4(fragColor.r*shadow, fragColor.g*shadow, fragColor.b*shadow, fragColor.a);
}
}
} else {
vec2 p = curlAxisLinePoint + mouseDir * (abs(dist) + pi * radius);
if (p.x <= aspect && p.y <= 1.0 && p.x > 0.0 && p.y > 0.0){
uv = p;
fragColor = texture(image, uv * vec2(1.0 / aspect, 1.0));
fragColor.rgb =mix(fragColor.rgb, vec3(1.0), 0.25);
} else {
fragColor = texture(image, uv * vec2(1.0 / aspect, 1.0));
if (p.x <= aspect+shadowWidth && p.y <= 1.0+shadowWidth&& p.x > 0.0-shadowWidth && p.y > 0.0-shadowWidth){
float shadow = calShadow(p, aspect);
fragColor = vec4(fragColor.r*shadow, fragColor.g*shadow, fragColor.b*shadow, fragColor.a);
}
}
}
if (distance(uv, vec2(0.0)) >(aspect-0.001)&&distance(uv, vec2(0.0))<(aspect+0.001)){
fragColor = TRANSPARENT;
}
}
小结
现在算是解决了翻页动画的几个大问题,接下来便是正式应用部分。不过在此之前,zoharSoul同学提出了一个很好的建议,ios上的图书App的翻页作用看上去比较人性化,或许在下一步应用之前,测验完成一下这个ios图书的翻页动画?