前言
近段時間我在工作中實現(xiàn)了一個動畫功能,其中涉及到動畫元素要按一定的軌跡在屏幕上移動,運(yùn)動軌跡的生成我使用了Path.cubicTo方法來實現(xiàn)的,這個方法其實是生成了一條有兩個控制點(diǎn)的貝塞爾曲線。借此機(jī)會我再收集了一些資料,想系統(tǒng)地學(xué)習(xí)一下貝塞爾曲線相關(guān)的知識。
簡介
貝塞爾曲線(Bézier curve)又被稱為貝茲曲線或貝濟(jì)埃曲線,是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學(xué)曲線,它的數(shù)學(xué)基礎(chǔ)是伯恩斯坦多項式(Bernstein polynomial,since 1912),1959年法國數(shù)學(xué)家Paul de Casteljau提出了數(shù)值穩(wěn)定的de Casteljau算法,開始貝塞爾曲線的圖形化應(yīng)用研究,而貝塞爾曲線的名稱來源于一位就職于雷諾的法國工程師Pierre Bézier,他在1962年開始對貝塞爾曲線做了廣泛的宣傳,他使用這種只需要很少的控制點(diǎn)就能生成復(fù)雜平滑曲線的方法來進(jìn)行汽車車體的工業(yè)設(shè)計。
貝塞爾曲線因為它控制簡便卻具有極強(qiáng)的描述能力,迅速在工業(yè)設(shè)計和計算機(jī)圖形學(xué)等相關(guān)領(lǐng)域得到了廣泛應(yīng)用。比如在矢量繪圖中,貝塞爾曲線用來給需要無限制地縮放的平滑曲線定模,許多繪圖軟件都提供了繪制貝塞爾曲線的功能。貝塞爾曲線還用于動畫時間控制以實現(xiàn)美觀逼真的緩動效果,還用于機(jī)器人轉(zhuǎn)動手臂等方面的設(shè)計。
de Casteljau算法原理
在向量AB上選擇一個點(diǎn)C,使得C分向量AB為u:1-u(即|AC|:|AB|=u)。給定點(diǎn)A、B的坐標(biāo)以及u(u∈[0,1])的值,點(diǎn)C的坐標(biāo)則為C=A+(B-A)*u=(1-u)*A+u*B。

貝塞爾曲線原理
利用de Casteljau算法可以計算貝塞爾曲線上的點(diǎn)C(u),u∈[0,1]。因此,通過給定一組u的值,即可算出貝塞爾曲線上的坐標(biāo)序列,從而繪制出貝塞爾曲線。
為了計算n階貝塞爾曲線上的點(diǎn)C(u),u∈[0,1],首先將控制點(diǎn)連接形成一條折線00-01-02······0(n-1)-0n,利用de Casteljau算法,計算出折線中每條線段0j-0(j+1)上的一個點(diǎn)1j,使得點(diǎn)1j分該線段的比為u:1-u,然后在折線10-11-12-······-1(n-1)上遞歸調(diào)用該算法,以此類推。最終求得一個點(diǎn)n0,已證明點(diǎn)n0一定是曲線上的點(diǎn)。

上圖中u=0.4,這從幾何形狀上形象地說明了de Casteljau算法的過程,可以將上述直觀的幾何描述表達(dá)為算術(shù)方法,如下圖所示:

首先將所有的控制點(diǎn)排成一列,如上圖最左列,每一對相鄰的控制點(diǎn)之間畫出兩個箭頭,分別指向右下方和右上方,在兩個箭頭交匯的位置記下一個新的點(diǎn)。比如控制點(diǎn)ij和i(j+1)生成新的控制點(diǎn)(i+1)j。指向右下方的箭頭表示乘以(1-u),指向右上方的箭頭表示乘以u。
將上述過程表達(dá)為下面的算法:
Input:arrayP[0:n] ofn+1 points and real numberuin [0,1]
Output:point on curve,C(u)
Working:point arrayQ[0:n]
fori:= 0 to n do
? ? Q[i] :=P[i]; // save input
fork:= 1 to n do
? ? fori:= 0ton - kdo
? ? ? ? Q[i] := (1 -u)Q[i] +uQ[i+ 1];
returnQ[0];
這個算法還可以被遞歸地表達(dá),上述點(diǎn)列還有其他有趣的性質(zhì),詳細(xì)描述請參考論文Finding a Point on a Bézier Curve: De Casteljau's Algorithm。
貝塞爾曲線展示
一階貝塞爾曲線

給定點(diǎn)P0和P1,一階貝塞爾曲線就是這兩點(diǎn)間的一條直線段,對應(yīng)公式:

二階貝塞爾曲線

給定點(diǎn)P0、P1和P2,二階貝塞爾曲線是由下述方程產(chǎn)生的軌跡

這個方程可以被描述為由兩段獨(dú)立的一階貝塞爾曲線再按de Casteljau算法合成的,分別是由 P0 至 P1 的連續(xù)點(diǎn) Q0描述一條線段,由 P1 至 P2 的連續(xù)點(diǎn) Q1描述另一條線段,最后由 Q0 至 Q1 的連續(xù)點(diǎn) B(t)描述了貝塞爾曲線。整理上述公式變成:

三階貝塞爾曲線

二維平面或者更高維度的空間中的4個點(diǎn)P0、P1、P2和P3可以定義一條三階的貝塞爾曲線,這條曲線從P0點(diǎn)開始向P1點(diǎn)方向運(yùn)動,最后從來自P2點(diǎn)的方向運(yùn)動到達(dá)P3點(diǎn)。一般點(diǎn)P1和P2不會在曲線上,它們用來控制曲線運(yùn)動的方向;點(diǎn)P0與P1間的距離將會影響曲線在轉(zhuǎn)向P2之前向P1點(diǎn)方向運(yùn)動的遠(yuǎn)近和快慢。按照二階貝塞爾曲線方程可以表達(dá)為一階的形式,三階貝塞爾曲線也可以由兩個二階貝塞爾曲線組合來線性地表達(dá):

將方程遞歸帶入整理得到:

更一般地,n階貝塞爾曲線的一般方程為:

例如,n=5時,即5階貝塞爾曲線的方程為:

貝塞爾曲線應(yīng)用
Android SDK中提供的API
在android.graphics包中的Path類提供了幾個可以繪制貝塞爾曲線的API,常用的就是lineTo、quadTo和cubicTo三個方法,分別可以繪制一、二、三階的貝塞爾曲線,一階沒什么可說的,就是通常的畫直線,傳入的參數(shù)就是目標(biāo)點(diǎn)坐標(biāo)。下面分別是quadTo和cubicTo方法的接口說明:


自定義貝塞爾工具
在實際項目中我自己寫了一個貝塞爾估值器類,核心方法如下:

后記
我第一次寫這樣長篇的技術(shù)博客,慚愧??!想以此加深自己的理解,也方便以后迅速查閱相關(guān)的資料。寫博客真的是一個深入學(xué)習(xí)的好方法,讓我不再流于解決問題的表面,通過查找資料可以透過直接的技術(shù)方案發(fā)現(xiàn)方案背后的邏輯和原理。理解了最本質(zhì)的東西,其他的都是可以舉一反三、觸類旁通的方法變幻。加油,少年!~
Thanks To:
Finding a Point on a Bézier Curve: De Casteljau's Algorithm