說來話長,這一切都得從PhotoShop中的鋼筆工具開始提及...git
聲明:本文不含複雜數學公式,學渣放心閱讀吧😂(我彷彿看到了學渣們留下了激動的淚水)程序員
貝塞爾曲線(Bézier curve)是應用於二維圖形應用程序的數學曲線,貝塞爾曲線基於多個點構成。它的應用很是普遍,好比說PS中的鋼筆工具所繪畫的曲線就是貝塞爾曲線,繪製動畫的運動軌跡等等,而最近一次想用到貝塞爾曲線是想作一個 路徑動畫 。github
在iOS開發中通常經過UIBezierPath
來實現貝塞爾曲線的繪製,平時通常使用繪製二階和三階貝塞爾曲線的方法。而咱們要作的遠超二三階的貝塞爾曲線,本文 iOS Demo在原理上實現了N階貝塞爾曲線的繪製,未使用任何相關API,純手動繪製貝塞爾曲線,而且能夠拖動滑塊瀏覽貝塞爾曲線的繪製過程。算法
本文 iOS Demo 實現如下功能:工具
實現功能 | 描述 |
---|---|
繪製貝塞爾曲線 | 一、點擊空白處設置貝塞爾曲線的點 二、能夠設置貝塞爾曲線階數 三、播放貝塞爾曲線繪製過程 四、拖動滑塊,自由查看繪製過程每個瞬間 |
簡易曲線圖表 | 每兩個點之間都是用3階貝塞爾曲線鏈接(細節待完善) |
過山車 | 一、在空白處繪製貝塞爾曲線 二、過山車沿着繪製的貝塞爾曲線行駛 三、支持多個鏈接的貝塞爾曲線路徑 |
Demo示例圖
動畫
說到繪製原理,若是貼👇這張圖,我只能說:什麼鬼!!!我看不懂,聽不見,你說什麼...
路人甲:簡單點...說話的方式簡單點~網站
首先提供一個能夠動態繪製貝塞爾曲線的網站幫助你更好地理解貝塞爾曲線的繪製。ui
貝塞爾曲線點的數量決定了曲線的階數,通常N個點構成的N-1階貝塞爾曲線,即3個點爲二階,至少由3個點組成,爲何兩個點不行,兩個點組成的是直線。按順序,第一個點爲 起點 ,最後一個點爲 終點 ,其他點都爲 控制點 。spa
這裏說的線不是貝塞爾曲線,而是各個點按順序鏈接起來,造成的直線,如上圖AB
、BC
兩條線。在這裏咱們要將整個曲線的繪製量化爲從0~1
的過程,用progress
爲當前過程的進度,progress
的區間即0~1
。每一條線都須要根據progress
生成一個點,以下圖,一個點從P0
移動到P1
,這是這條線從0~1
的過程。
3d
下面是繪製一個二階貝塞爾曲線過程,先給口訣: 點生線,線生點 😂。由A
、B
、C
這3個點組成2條線AB
和BC
,2條線根據progress
分別生成2個移動的點D
和E
,而D
和E
又連成一條線,始終保持AD:DB=BE:EC
。
DE
,DE
再根據progress
生成點F
,只剩一個點,沒法構成線,即爲最終構成貝塞爾曲線的點。紅色點爲progress
在0~1
過程當中點F
的移動過程,保持AD:DB=BE:EC=DF:FE
。
通過上面 點生線,線生點 的過程 ,咱們拿到了點F在移動中全部點的,將這些點集合鏈接起來,即造成了貝塞爾曲線。progress
自增越慢,點集合的點越多,曲線就越細緻。
稍微瞭解算法的同窗就能發現,其實 點生線,線生點 是一個遞歸的過程,經過底層的點,一步步推算出最高階的點。整個推導過程像一個金字塔,底部點的數量最多,每高一階點的數量就減1,直至最高階只有1個點。
下面是遞歸代碼:
// 貝塞爾曲線每高一階 須要遞歸次數+1
+ (NSArray *)recursionGetsubLevelPointsWithSuperPoints:(NSArray *)points progress:(CGFloat)progress{
// 獲得最終的點 正確結束遞歸
if (points.count == 1) return points;
NSMutableArray *tempArr = [[NSMutableArray alloc] init];
for (int i = 0; i < points.count-1; i++) {
// 第一個點
NSValue *preValue = [points objectAtIndex:i];
CGPoint prePoint = preValue.CGPointValue;
// 第二個點
NSValue *lastValue = [points objectAtIndex:i+1];
CGPoint lastPoint = lastValue.CGPointValue;
// 兩點座標差
CGFloat diffX = lastPoint.x-prePoint.x;
CGFloat diffY = lastPoint.y-prePoint.y;
// 根據當前progress得出高一階的點
CGPoint currentPoint = CGPointMake(prePoint.x+diffX*progress, prePoint.y+diffY*progress);
[tempArr addObject:[NSValue valueWithCGPoint:currentPoint]];
}
// 繼續下一次遞歸過程
return [self recursionGetsubLevelPointsWithSuperPoints:tempArr progress:progress];
}複製代碼
8階貝塞爾曲線繪製過程:
光講原理脫離實踐這不是程序員的風格,簡單地寫了2個貝塞爾曲線的應用,都在本文 iOS Demo 裏面,歡迎運行體驗。
經過點擊屏幕收集點,將點集合生成貝塞爾曲線,可生成多個相連的貝塞爾曲線。小車按照生成的貝塞爾曲線路徑前進。
a. 畫路徑
經過計算貝塞爾曲線的長度,根據曲線長度分配點的數量,達到點的相對均勻分佈,使過山車 勻速前進 。
b. 發車
每一個點都與前面一個點連線,經過計算得出兩點的連線與水平造成的夾角,將角度賦予過山車實現 轉向功能 。
a. 直線圖表
即最簡單的兩點連成直線。
b. 曲線圖表
曲線圖表的曲線所有由3階貝塞爾曲線構成,整個曲線圖不含任何棱角。
iOS
畫路徑神器
PaintCode
,畫好圖形直接生成代碼,用鋼筆工具畫貝塞爾曲線也十分方便。下圖爲用鋼筆工具畫一個圓球(貌似不夠圓😆):
爲了準備這一篇文章差很少理解了貝塞爾曲線的繪製原理,可是在細節處,好比說真正意義上貝塞爾曲線點的均勻分佈還有待完善,求曲線公式也沒有去研究,貝塞爾曲線在複雜的動畫方向地應用也是大有做爲。
我的水平有限,歡迎提出建議。