說(shuō)來(lái)話長(zhǎng),這一切都得從PhotoShop中的鋼筆工具開始說(shuō)起...
聲明:本文不含復(fù)雜數(shù)學(xué)公式,學(xué)渣放心閱讀吧??(我仿佛看到了學(xué)渣們留下了激動(dòng)的淚水)
背景
貝塞爾曲線(Bézier curve)是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學(xué)曲線,貝塞爾曲線基于多個(gè)點(diǎn)構(gòu)成。它的應(yīng)用非常廣泛,比如說(shuō)PS中的鋼筆工具所繪畫的曲線就是貝塞爾曲線,繪制動(dòng)畫的運(yùn)動(dòng)軌跡等等,而最近一次想用到貝塞爾曲線是想做一個(gè) 路徑動(dòng)畫 。
簡(jiǎn)介
在iOS開發(fā)中一般通過(guò)UIBezierPath
來(lái)實(shí)現(xiàn)貝塞爾曲線的繪制,平時(shí)一般使用繪制二階和三階貝塞爾曲線的方法。而我們要做的遠(yuǎn)超二三階的貝塞爾曲線,本文 iOS Demo在原理上實(shí)現(xiàn)了N階貝塞爾曲線的繪制,未使用任何相關(guān)API,純手動(dòng)繪制貝塞爾曲線,并且可以拖動(dòng)滑塊瀏覽貝塞爾曲線的繪制過(guò)程。
本文 iOS Demo 實(shí)現(xiàn)以下功能:
實(shí)現(xiàn)功能 | 描述 |
---|---|
繪制貝塞爾曲線 | 1、點(diǎn)擊空白處設(shè)置貝塞爾曲線的點(diǎn) </br>2、可以設(shè)置貝塞爾曲線階數(shù) </br>3、播放貝塞爾曲線繪制過(guò)程 </br> 4、拖動(dòng)滑塊,自由查看繪制過(guò)程每一個(gè)瞬間 |
簡(jiǎn)易曲線圖表 | 每?jī)蓚€(gè)點(diǎn)之間都是用3階貝塞爾曲線連接(細(xì)節(jié)待完善) |
過(guò)山車 | 1、在空白處繪制貝塞爾曲線 </br>2、過(guò)山車沿著繪制的貝塞爾曲線行駛</br>3、支持多個(gè)連接的貝塞爾曲線路徑 |
Demo示例圖
貝塞爾曲線的繪制原理
說(shuō)到繪制原理,如果貼??這張圖,我只能說(shuō):什么鬼!!!我看不懂,聽不見,你說(shuō)什么...
路人甲:簡(jiǎn)單點(diǎn)...說(shuō)話的方式簡(jiǎn)單點(diǎn)~
首先提供一個(gè)可以動(dòng)態(tài)繪制貝塞爾曲線的網(wǎng)站幫助你更好地理解貝塞爾曲線的繪制。
1. 點(diǎn)
貝塞爾曲線點(diǎn)的數(shù)量決定了曲線的階數(shù),一般N個(gè)點(diǎn)構(gòu)成的N-1階貝塞爾曲線,即3個(gè)點(diǎn)為二階,至少由3個(gè)點(diǎn)組成,為什么兩個(gè)點(diǎn)不行,兩個(gè)點(diǎn)組成的是直線。按順序,第一個(gè)點(diǎn)為 起點(diǎn) ,最后一個(gè)點(diǎn)為 終點(diǎn) ,其余點(diǎn)都為 控制點(diǎn) 。
2. 點(diǎn)生線
這里說(shuō)的線不是貝塞爾曲線,而是各個(gè)點(diǎn)按順序連接起來(lái),形成的直線,如上圖AB
、BC
兩條線。在這里我們要將整個(gè)曲線的繪制量化為從0~1
的過(guò)程,用progress
為當(dāng)前過(guò)程的進(jìn)度,progress
的區(qū)間即0~1
。每一條線都需要根據(jù)progress
生成一個(gè)點(diǎn),如下圖,一個(gè)點(diǎn)從P0
移動(dòng)到P1
,這是這條線從0~1
的過(guò)程。
下面是繪制一個(gè)二階貝塞爾曲線過(guò)程,先給口訣: 點(diǎn)生線,線生點(diǎn) ??。由A
、B
、C
這3個(gè)點(diǎn)組成2條線AB
和BC
,2條線根據(jù)progress
分別生成2個(gè)移動(dòng)的點(diǎn)D
和E
,而D
和E
又連成一條線,始終保持AD:DB=BE:EC
。
DE
,DE
再根據(jù)progress
生成點(diǎn)F
,只剩一個(gè)點(diǎn),無(wú)法構(gòu)成線,即為最終構(gòu)成貝塞爾曲線的點(diǎn)。紅色點(diǎn)為progress
在0~1
過(guò)程中點(diǎn)F
的移動(dòng)過(guò)程,保持AD:DB=BE:EC=DF:FE
。
3. 繪制貝塞爾曲線
經(jīng)過(guò)上面 點(diǎn)生線,線生點(diǎn) 的過(guò)程 ,我們拿到了點(diǎn)F在移動(dòng)中所有點(diǎn)的,將這些點(diǎn)集合連接起來(lái),即形成了貝塞爾曲線。progress
自增越慢,點(diǎn)集合的點(diǎn)越多,曲線就越細(xì)致。
4. N階貝塞爾曲線
稍微了解算法的同學(xué)就能發(fā)現(xiàn),其實(shí) 點(diǎn)生線,線生點(diǎn) 是一個(gè)遞歸的過(guò)程,通過(guò)底層的點(diǎn),一步步推算出最高階的點(diǎn)。整個(gè)推導(dǎo)過(guò)程像一個(gè)金字塔,底部點(diǎn)的數(shù)量最多,每高一階點(diǎn)的數(shù)量就減1,直至最高階只有1個(gè)點(diǎn)。
**下面是遞歸代碼: **
// 貝塞爾曲線每高一階 需要遞歸次數(shù)+1
+ (NSArray *)recursionGetsubLevelPointsWithSuperPoints:(NSArray *)points progress:(CGFloat)progress{
// 得到最終的點(diǎn) 正確結(jié)束遞歸
if (points.count == 1) return points;
NSMutableArray *tempArr = [[NSMutableArray alloc] init];
for (int i = 0; i < points.count-1; i++) {
// 第一個(gè)點(diǎn)
NSValue *preValue = [points objectAtIndex:i];
CGPoint prePoint = preValue.CGPointValue;
// 第二個(gè)點(diǎn)
NSValue *lastValue = [points objectAtIndex:i+1];
CGPoint lastPoint = lastValue.CGPointValue;
// 兩點(diǎn)坐標(biāo)差
CGFloat diffX = lastPoint.x-prePoint.x;
CGFloat diffY = lastPoint.y-prePoint.y;
// 根據(jù)當(dāng)前progress得出高一階的點(diǎn)
CGPoint currentPoint = CGPointMake(prePoint.x+diffX*progress, prePoint.y+diffY*progress);
[tempArr addObject:[NSValue valueWithCGPoint:currentPoint]];
}
// 繼續(xù)下一次遞歸過(guò)程
return [self recursionGetsubLevelPointsWithSuperPoints:tempArr progress:progress];
}
8階貝塞爾曲線繪制過(guò)程:
貝塞爾曲線的應(yīng)用
光講原理脫離實(shí)踐這不是程序員的風(fēng)格,簡(jiǎn)單地寫了2個(gè)貝塞爾曲線的應(yīng)用,都在本文 iOS Demo 里面,歡迎運(yùn)行體驗(yàn)。
1. 過(guò)山車
通過(guò)點(diǎn)擊屏幕收集點(diǎn),將點(diǎn)集合生成貝塞爾曲線,可生成多個(gè)相連的貝塞爾曲線。小車按照生成的貝塞爾曲線路徑前進(jìn)。
a. 畫路徑
通過(guò)計(jì)算貝塞爾曲線的長(zhǎng)度,根據(jù)曲線長(zhǎng)度分配點(diǎn)的數(shù)量,達(dá)到點(diǎn)的相對(duì)均勻分布,使過(guò)山車 勻速前進(jìn) 。
b. 發(fā)車
每個(gè)點(diǎn)都與前面一個(gè)點(diǎn)連線,通過(guò)計(jì)算得出兩點(diǎn)的連線與水平形成的夾角,將角度賦予過(guò)山車實(shí)現(xiàn) 轉(zhuǎn)向功能 。
2. 簡(jiǎn)易曲線圖表
a. 直線圖表
即最簡(jiǎn)單的兩點(diǎn)連成直線。
b. 曲線圖表
曲線圖表的曲線全部由3階貝塞爾曲線構(gòu)成,整個(gè)曲線圖不含任何棱角。
拓展
推薦一個(gè)
iOS
畫路徑神器PaintCode
,畫好圖形直接生成代碼,用鋼筆工具畫貝塞爾曲線也十分方便。下圖為用鋼筆工具畫一個(gè)圓球(貌似不夠圓??):
總結(jié)
為了準(zhǔn)備這一篇文章差不多理解了貝塞爾曲線的繪制原理,但是在細(xì)節(jié)處,比如說(shuō)真正意義上貝塞爾曲線點(diǎn)的均勻分布還有待完善,求曲線公式也沒(méi)有去研究,貝塞爾曲線在復(fù)雜的動(dòng)畫方向地應(yīng)用也是大有作為。
參考
貝塞爾曲線開發(fā)的藝術(shù)
Android:貝塞爾曲線原理分析
個(gè)人水平有限,歡迎提出建議。