这些天pop 动画源码,里面涉及到贝塞尔曲线。对贝塞尔曲线以前只是局限在会用的阶段,没仔细详细研究过,今天准备仔细看看,搞明白。
贝赛尔曲线 概念
贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋。
贝塞尔曲线的公式
贝塞尔曲线通用公式术语
一些关于参数曲线的术语,有
image.png
即多项式
image.png
又称作n阶的伯恩斯坦基底多项式,定义00 = 1。
点Pi称作贝济埃曲线的控制点。多边形以带有线的贝济埃点连接而成,起始于P0并以Pn终止,称作贝济埃多边形(或控制多边形)。贝济埃多边形的凸包包含有贝济埃曲线。
二项式定理
这里我看到
image.png
一脸懵逼,虽然以前学过,但是忘记了,因此这里查了半天才知道这么写法是什么意思了。
image.png看懂这个图就明白二项式前面的项了
注解
- 开始于P0并结束于Pn的曲线,即所谓的端点插值法属性。
- 曲线是直线的充分必要条件是所有的控制点都位在曲线上。同样的,贝济埃曲线是直线的充分必要条件是控制点共线。
- 曲线的起始点结束点相切于贝济埃多边形。
- 一条曲线可在任意点切割成两条或任意多条子曲线,每一条子曲线仍是贝济埃曲线。
- 一些看似简单的曲线(如圆)无法以贝济埃曲线精确的描述,或分段成贝济埃曲线。
- 位于固定偏移量的曲线(来自给定的贝济埃曲线),又称作偏移曲线(假平行于原来的曲线,如两条铁轨之间的偏移)无法以贝济埃曲线精确的形成(某些平凡实例除外)。无论如何,现存的启发法通常可为实际用途中给出近似值。
基本公式的低阶公式
线性公式 (当n=1)时候
一阶给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。
二次方公式
image.png三次方公式
image.png构建贝塞尔曲线
线型曲线
线性贝塞尔曲线函数中的t会经过由P0至P1的B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P0至P1路径的四分之一处。就像由0至1的连续t,B(t)描述一条由P0至P1的直线。
线型贝塞尔曲线二次曲线
为建构二次贝塞尔曲线,可以中介点Q0和Q1作为由0至1的t:
由P0至P1的连续点Q0,描述一条线性贝塞尔曲线。
由P1至P2的连续点Q1,描述一条线性贝塞尔曲线。
由Q0至Q1的连续点B(t),描述一条二次贝塞尔曲线。
三阶贝塞尔曲线
三次曲线,可由线性贝济埃曲线描述的中介点Q0、Q1、Q2,和由二次曲线描述的点R0、R1所建构
高阶贝塞尔曲线
对于四次曲线,可由线性贝济埃曲线描述的中介点Q0、Q1、Q2、Q3,由二次贝济埃曲线描述的点R0、R1、R2,和由三次贝济埃曲线描述的点S0、S1所建构
升阶
n次贝济埃曲线可以转换为一个形状完全相同的n+1次贝济埃曲线。 这在软件只支援特定阶次的贝济埃曲线时很有用。 例如,Cairo只支援三次贝济埃曲线,你就可以用升阶的方法在Cairo画出二次贝济埃曲线。
我们利用
这个特性来做升阶。我们把曲线方程中每一项 都乘上 (1 − t) 或 t,让每一项都往上升一阶。以下是将二阶升为三阶的范例这里我估计好多很看着很懵,我也懵,仔细看就明白了,将上述公式的省略项补齐就好看了。
(1-t)2P0 + 2(1-t)tP1 + t2P2 这是二阶的公式
推导出三阶如下
(1-t)2P0 + 2(1-t)tP1 + t2P2
= (1-t)2P0[(1-t)+t] + 2(1-t)tP1[(1-t)+t] + t2P2[(1-t)+t]
= (1-t)2P0(1-t) + (1-t)2P0t + 2(1-t)tP1(1-t) + 2(1-t)tP1t + t2P2(1-t) + t2P2t
=(1-t)3P0 + (1-t)2tP0 + 2(1-t)2tP1 + 2(1-t)t2P1 + (1-t)t2P2 + t3P2
整理
=(1-t)3P0 + [(1-t)2tP0 + 2(1-t)2tP1] + [2(1-t)t2P1 + (1-t)t2P2] + t3P2
=(1-t)3P0 + (1-t)2t(P0+2P1) + (1-t)t2(2P1+P2) + t3P2
=(1-t)3P0 + [(1-t)2tP0 + 2(1-t)2tP1] + [2(1-t)t2P1 + (1-t)t2P2] + t3P2
=(1-t)3P0 + 3(1-t)2t(P0+2P1)/3 + 3(1-t)t2(2P1+P2)/3 + t3P2
通过上面的规律下面的也是一样的计算获取高阶
对任何的n值,我们都可以使用以下等式
式中P-1 和Pn+1 可以任意挑选。
因此,新的控制点为
程序范例
三阶贝塞尔曲线
namespace WebCore {
struct UnitBezier {
UnitBezier(double p1x, double p1y, double p2x, double p2y)
{
// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
/// 三阶贝塞尔曲线系数计算,推导在pop动画分析里讲解的
cx = 3.0 * p1x;
bx = 3.0 * (p2x - p1x) - cx;
ax = 1.0 - cx -bx;
cy = 3.0 * p1y;
by = 3.0 * (p2y - p1y) - cy;
ay = 1.0 - cy - by;
}
/// 时间对应的点x
double sampleCurveX(double t)
{
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((ax * t + bx) * t + cx) * t;
}
///时间对应的点y
double sampleCurveY(double t)
{
return ((ay * t + by) * t + cy) * t;
}
///x的倒数
double sampleCurveDerivativeX(double t)
{
return (3.0 * ax * t + 2.0 * bx) * t + cx;
}
// Given an x value, find a parametric value it came from.
double solveCurveX(double x, double epsilon)
{
double t0;
double t1;
double t2;
double x2;
double d2;
int i;
// First try a few iterations of Newton's method -- normally very fast.
///
for (t2 = x, i = 0; i < 8; i++) {
///获取t2 时间x 周的坐标
x2 = sampleCurveX(t2) - x;
NSLog(@"%lf %lf %lf",t2 , x ,x2);
if (fabs (x2) < epsilon)
return t2;
NSLog(@"====%lf",x2);
///计算斜率
d2 = sampleCurveDerivativeX(t2);
NSLog(@"d2====%lf",d2);
if (fabs(d2) < 1e-6)
break;
///
t2 = t2 - x2 / d2;
}
NSLog(@"error");
// Fall back to the bisection method for reliability.
t0 = 0.0;
t1 = 1.0;
t2 = x;
if (t2 < t0)
return t0;
if (t2 > t1)
return t1;
while (t0 < t1) {
x2 = sampleCurveX(t2);
if (fabs(x2 - x) < epsilon)
return t2;
if (x > x2)
t0 = t2;
else
t1 = t2;
t2 = (t1 - t0) * .5 + t0;
}
// Failure.
return t2;
}
double solve(double x, double epsilon)
{
return sampleCurveY(solveCurveX(x, epsilon));
}
private:
double ax;
double bx;
double cx;
double ay;
double by;
double cy;
};
}
ios的三阶贝塞尔曲线
苹果提供了关于三阶贝塞尔曲线的类CAMediaTimingFunction
从这个类中我们读取到苹果规定的
CA_EXTERN NSString * const kCAMediaTimingFunctionLinear
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAMediaTimingFunctionEaseIn
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAMediaTimingFunctionEaseOut
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAMediaTimingFunctionEaseInEaseOut
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAMediaTimingFunctionDefault
样式的贝塞尔曲线的顶点值。
- (void)getControlPointAtIndex:(size_t)idx values:(float[_Nonnull 2])ptr;
该函数是获取顶点的api
该类使用在layer动画中
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化layer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(50, 50, 200, 2);
layer.backgroundColor = [UIColor blackColor].CGColor;
// 终点位置
CGPoint endPosition = CGPointMake(layer.position.x, layer.position.y + 200);
// 动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fromValue = [NSValue valueWithCGPoint:layer.position];
animation.toValue = [NSValue valueWithCGPoint:endPosition];
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.20 :0.03 :0.13 :1.00];
layer.position = endPosition;
animation.duration = 1.f;
// 添加动画
[layer addAnimation:animation forKey:nil];
// 添加layer
[self.view.layer addSublayer:layer];
}
绘制贝塞尔曲线与时间的关系
坐标系没有进行转换,坐标原点再左上角
float cx;
float bx ;
float ax;
float cy ;
float by ;
float ay ;
@interface DrawPoint()
{
}
@property (nonatomic,assign) CGPoint control1;
@property (nonatomic,assign) CGPoint control2;
@end
@implementation DrawPoint
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor redColor];
CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
float point[2];
for (int i =0;i<4;i++) {
float point[2];
[function getControlPointAtIndex:i values:point];
self.control1 = CGPointMake(point[0], point[1]);
NSLog(@"%@",NSStringFromCGPoint(self.control1));
}
[function getControlPointAtIndex:1 values:point];
self.control1 = CGPointMake(point[0], point[1]);
[function getControlPointAtIndex:2 values:point];
self.control2 = CGPointMake(point[0], point[1]);
cx = 3.0 * self.control1.x;
bx = 3.0 * (self.control2.x - self.control1.x) - cx;
ax = 1.0 - cx -bx;
cy = 3.0 * self.control1.y;
by = 3.0 * (self.control2.y - self.control1.y) - cy;
ay = 1.0 - cy - by;
}
return self;
}
double sampleCurveX(double t)
{
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((ax * t + bx) * t + cx) * t;
}
///时间对应的点y
double sampleCurveY(double t)
{
return ((ay * t + by) * t + cy) * t;
}
- (void)drawRect:(CGRect)rect {
UIColor *color = [UIColor colorWithRed:0 green:0 blue:0.7 alpha:1];
[color set];
int n = 1000;
for (int i=0; i<n; i++) {
UIBezierPath* aPath = [UIBezierPath bezierPathWithRect:CGRectMake(sampleCurveX(i*1.0/n)*self.bounds.size.width, sampleCurveY(i*1.0/n)*self.bounds.size.height, 1, 1)]; // 2.创建图形相应的
[aPath fill];
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
*/
@end
上面是测试代码
kCAMediaTimingFunctionLinear
image.png2018-09-18 16:40:56.085033+0800 draw[16389:15584186] {0, 0}
2018-09-18 16:40:56.086905+0800 draw[16389:15584186] {0, 0}
2018-09-18 16:40:56.087094+0800 draw[16389:15584186] {1, 1}
2018-09-18 16:40:56.087281+0800 draw[16389:15584186] {1, 1}
kCAMediaTimingFunctionEaseIn
image.png2018-09-18 16:40:12.168905+0800 draw[16362:15583293] {0, 0}
2018-09-18 16:40:12.169112+0800 draw[16362:15583293] {0.41999998688697815, 0}
2018-09-18 16:40:12.169238+0800 draw[16362:15583293] {1, 1}
2018-09-18 16:40:12.169362+0800 draw[16362:15583293] {1, 1}
kCAMediaTimingFunctionEaseOut
image.png2018-09-18 16:39:31.150050+0800 draw[16336:15582505] {0, 0}
2018-09-18 16:39:31.150593+0800 draw[16336:15582505] {0, 0}
2018-09-18 16:39:31.151359+0800 draw[16336:15582505] {0.57999998331069946, 1}
2018-09-18 16:39:31.151518+0800 draw[16336:15582505] {1, 1}
kCAMediaTimingFunctionEaseInEaseOut
image.png2018-09-18 16:38:36.048302+0800 draw[16305:15581503] {0, 0}
2018-09-18 16:38:36.048784+0800 draw[16305:15581503] {0.41999998688697815, 0}
2018-09-18 16:38:36.049117+0800 draw[16305:15581503] {0.57999998331069946, 1}
2018-09-18 16:38:36.049575+0800 draw[16305:15581503] {1, 1}
kCAMediaTimingFunctionDefault
image.png2018-09-18 16:38:05.253326+0800 draw[16284:15580826] {0, 0}
2018-09-18 16:38:05.253511+0800 draw[16284:15580826] {0.25, 0.10000000149011612}
2018-09-18 16:38:05.253643+0800 draw[16284:15580826] {0.25, 1}
2018-09-18 16:38:05.253774+0800 draw[16284:15580826] {1, 1}
自定义几个值看看
CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithControlPoints:0.25 :0.5 :0.75 :0.5];
image.png
网友评论