一、什么是贝塞尔曲线

1962年,法国工程师皮埃尔·贝塞尔(Pierre Bézier),贝塞尔曲线来为为解决汽车的主体的设计问题而发明了贝塞尔曲线。如今,贝赛尔曲线是计算机图形学中相当重要的一种曲线,它能过优雅地模拟人手绘画出的线。它通过控制曲线上的点(起始点、终止点以及多个参考点)来创造、编辑图形。其中起重要作用的是位于曲线中央的控制线。这条线是虚拟的,中间与贝塞尔曲线交叉,两端是控制端点。移动两端的端点时贝塞尔曲线改变曲线的曲率(弯曲的程度);移动中间点(也就是移动虚拟的控制线)时,贝塞尔曲线在起始点和终止点锁定的情况下做均匀移动。

二、贝塞尔曲线的应用

  • 贝赛尔曲线广泛应用于绘图软件中,例如Adobe PhotoShop、Adobe Flash。

  • Android可以通过自定义的view来实现贝塞尔曲线

  • ios则可以使用UIBezierPath类来生成贝塞尔曲线

  • 前端,canvas bezierCurveTo,css animation-timing-function: cubic-bezier(x,x,x,x}都有关于贝赛尔曲线的一些应用

三、贝塞尔曲线公式及其分析

一次:

image

image

二次:

image

image

三次:

image

image

n次

image

但是公式中只是给出了点与点之间的关系,并没有给出y与x坐标的关系,为此我们需要对其进行分解,下面以三次贝塞尔曲线为例子:

image

四、贝塞尔曲线在动画中的应用

image

如图,X轴用来表示时间,Y轴用来表示动画的完成度。所以贝塞尔曲线在动画中的应用很简单,每隔一定时间传入当前动画的执行时间占总时间的比例然后就可以得出动画在此时刻完成的程度,最后只需要设置一下此时刻动画的完成度就行了。由于它的起始坐标分别为(0,0),(1,1)。

image

JS代码实现

我们已经求出了三次贝塞尔曲线x,y与t的关系,那么就可以根据这个关系通过JS实现

function UnitBezier(p1x,p1y,p2x,p2y) {
        this.cx = 3.0 * p1x;
        this.bx = 3.0 * (p2x - p1x) - this.cx;
        this.ax = 1.0 - this.cx -this.bx;    
        this.cy = 3.0 * p1y;
        this.by = 3.0 * (p2y - p1y) - this.cy;
        this.ay = 1.0 - this.cy - this.by;
}
UnitBezier.prototype = {
    sampleCurveX : function(t) {
        return ((this.ax * t + this.bx) * t + this.cx) * t;
    },
    sampleCurveY : function(t) {  
        return ((this.ay * t + this.by) * t + this.cy) * t;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可能到这里感觉上,我们已经实现了贝塞尔曲线的在动画上的应用了,我们看下这个demo.

image

然而我们发现原始JS实现的动画效果明显不同于CSS3中的动画效果。那么该如何去模拟了?看下面的代码:

function UnitBezier(p1x,p1y,p2x,p2y) {
        this.cx = 3.0 * p1x;
        this.bx = 3.0 * (p2x - p1x) - this.cx;
        this.ax = 1.0 - this.cx -this.bx;    
        this.cy = 3.0 * p1y;
        this.by = 3.0 * (p2y - p1y) - this.cy;
        this.ay = 1.0 - this.cy - this.by;
}
UnitBezier.prototype = {
    sampleCurveX : function(t) { //贝赛尔曲线t时刻的坐标点的X坐标
        return ((this.ax * t + this.bx) * t + this.cx) * t;
    },
    sampleCurveY : function(t) {  //贝赛尔曲线t时刻的坐标点的y坐标
        return ((this.ay * t + this.by) * t + this.cy) * t;
    },
    solve:function(t){
        this.sampleCurveY(this.sampleCurveX(t))
     }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

我们再来测试一下看下效果

image

额,这下差别更大。但是仔细想想这里所有的数据基本都是浮点运算,那么产生的误差可想而知。

五、浮点型运算误差

在计算机中浮点型运算造成的误差非常普遍,不管是C、C++、java、javaScript等待语言都存在,因为都是按照IEEE 754标准来进行浮点运算。

IEEE 754

按照IEEE 754 标准,32位中有:

  • 1位是符号位(sign)
  • 8位是指数位(exponent)
  • 23位是数值 (fraction)

image

那么计算出的结果为:
image

举些栗子

比如 0.1的单精度浮点数在计算机二进制数为:

0 01111011 10011001100110011001101

那么实际值为

image

比如 0.5的单精度浮点数在计算机二进制数为:

0 01111110 00000000000000000000000

六、怎样缩小浮点型运算产生的误差

首先我们可以来看下最简单的减少误差的方法

放大法

由于浮点型运算会产生误差,因此我们可以变相地将它放大,知道它为整数,最后返回的时候在将值还原,而这种方法是为完全将误差消除的。


Math.formatFloat = function(f, digit) {
var m = Math.pow(10, digit);
return parseInt(f*m,10) m;
}

var numA = 0.1;
var numB = 0.2;
alert(Math.formatFloat(numA+numB,1)===0.3);

虽然这种方法在JS中能够很好地消除误差,但是并不是所有的场景均适应。它比较时候简单的加减运算和位数较低的乘法除法运算,这是因为在JS中精确整数的范围被定义为:−9007199254740992到9007199254740992(2的53次方),也就是说最多16位。然而我们的贝塞尔曲线应用在动画的过程中肯定会产生位数超过16位的浮点数,这种方式仍然不能解决问题。

二分法

二分法是我们在高中的时候老师就已经给我们讲过,不过那个时候通常是用来判断f(x0)是正数还是负数,有时候还会给出精度要求。在这里我们同样也可以使用二分法来解决,我们只需要不断地利用二分法找出(f(x0)-x)

function division(x,t1,t2, epsilon,func) {//计算x的近似值
        var t0,
            t1,
            t2,
            x2,
            i;
            t2 = x;
            if (t2 < t0) 
                return t0;
            if (t2 > t1)
                return t1;
            while (t0 < t1) {
                x2 = func(t2);
                if (Math.abs(x2 - x) < epsilon)
                    return t2;
                if (x > x2)
                    t0 = t2;
                else t1 = t2;
                    t2 = (t1 - t0) * .5 + t0;
            }
        return t2;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

牛顿迭代法

牛顿迭代法也是常常在计算机中减少误差的一种常用方法,公式如下:

设r是f(x)=0的根,选取x0作为r的初始近似值,过点(x0,f(x0))做曲线 的切线L,L的方程为y=f(x0)+f’(x0)(x-x0),求出L与x轴交点的横坐标 x1=x0-f(x0)/f’(x0) ,称x1为r的一次近似值。过点 做曲线 的切线,并求该切线与x轴交点的横坐标 ,称 为r的二次近似值。重复以上过程,得r的近似值序列,其中,xn+1=xn-f(xn)/f’(xn)称为r的n+1次近似值。

在处理贝塞尔曲线的过程中,由于我们不可能求出真正准确的值,为此我们只需要满足f(x0)-x

最终的效果

最后已经非常接近CSS3自带的贝塞尔曲线动画效果了,虽然仍有一点差别,这是因为采取的方法只能使误差只能缩小而不能被消除,而且精度越多,那么JS动画性能就越差,所以这里精度为设定为0.01.

image

原文见这里(http://www.yuchenblog.cn/?p=102)