一、什么是貝塞爾曲線
1962年,法國工程師皮埃爾·貝塞爾(Pierre Bézier),貝塞爾曲線來為為解決汽車的主體的設計問題而發明了貝塞爾曲線。如今,貝賽爾曲線是計算機圖形學中相當重要的一種曲線,它能過優雅地模擬人手繪畫出的線。它通過控制曲線上的點(起始點、終止點以及多個參考點)來創造、編輯圖形。其中起重要作用的是位於曲線中央的控制線。這條線是虛擬的,中間與貝塞爾曲線交叉,兩端是控制端點。移動兩端的端點時貝塞爾曲線改變曲線的曲率(彎曲的程度);移動中間點(也就是移動虛擬的控制線)時,貝塞爾曲線在起始點和終止點鎖定的情況下做均勻移動。
二、貝塞爾曲線的應用
-
貝賽爾曲線廣泛應用於繪圖軟件中,例如Adobe PhotoShop、Adobe Flash。
-
Android可以通過自定義的view來實現貝塞爾曲線
-
ios則可以使用UIBezierPath類來生成貝塞爾曲線
-
前端,canvas bezierCurveTo,css animation-timing-function: cubic-bezier(x,x,x,x}都有關於貝賽爾曲線的一些應用
三、貝塞爾曲線公式及其分析
一次:
二次:
三次:
n次
但是公式中只是給出了點與點之間的關系,並沒有給出y與x坐標的關系,為此我們需要對其進行分解,下面以三次貝塞爾曲線為例子:
四、貝塞爾曲線在動畫中的應用
如圖,X軸用來表示時間,Y軸用來表示動畫的完成度。所以貝塞爾曲線在動畫中的應用很簡單,每隔一定時間傳入當前動畫的執行時間占總時間的比例然后就可以得出動畫在此時刻完成的程度,最后只需要設置一下此時刻動畫的完成度就行了。由於它的起始坐標分別為(0,0),(1,1)。
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.
然而我們發現原始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
我們再來測試一下看下效果
額,這下差別更大。但是仔細想想這里所有的數據基本都是浮點運算,那么產生的誤差可想而知。
五、浮點型運算誤差
在計算機中浮點型運算造成的誤差非常普遍,不管是C、C++、java、javaScript等待語言都存在,因為都是按照IEEE 754標准來進行浮點運算。
IEEE 754
按照IEEE 754 標准,32位中有:
- 1位是符號位(sign)
- 8位是指數位(exponent)
- 23位是數值 (fraction)
那么計算出的結果為:
舉些栗子
比如 0.1的單精度浮點數在計算機二進制數為:
0 01111011 10011001100110011001101
那么實際值為
比如 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.