使用貝塞爾曲線繪制多點連接曲線

背景:

? ? ? ?給一系列頂點,如果只是用直線將其中的各個點依次連接起來,最終形成一個折線圖,這種很容易實現。但是現實中事物的變化往往具有連續的特性,即使是給定了一系列離散的點,基于以往的生活經驗,人們也更愿意接受那種曲線連接的趨勢圖。可是在程序中繪制直線很容易,要是繪制曲線將各個頂點連接起來,這又要如何實現呢?一種很直觀的思路就是將連接各點的直線替換成平滑的曲線,只要各段曲線在頂點處是平滑的過度,那么對應的曲線圖就是所需的了。因此問題變成了尋找一種容易實現的曲線來連接各個頂點。

工具--貝塞爾曲線:

? ? ? ?計算機圖形學中有一類很常用的曲線,俗稱貝塞爾曲線。1962年,法國數學家Pierre Bézier第一個研究了這種矢量繪制曲線的方法,并給出了詳細的計算公式,因此按照這樣的公式繪制出來的曲線就用他的姓氏來命名是為貝塞爾曲線。很多程序語言都有實現貝塞爾曲線的API,而該曲線本身也擁有強大的近似其它曲線的能力,即使一條不能夠勝任,那么分段的多條貝塞爾曲線也足夠用來近似我們想繪制的曲線。

貝塞爾曲線數學表示:

? 一階貝塞爾曲線:給定點P0、P1,一階貝塞爾曲線只是一條兩點之間的直線。這條線由下式給出: ? ? ? ? ? ? ? ??

其中P0和P1為兩個端點,P對應于貝塞爾曲線上的點,隨著t在[0,1]中變化,P點的集合構成一條連接P0與P1的線段。

二階貝塞爾曲線:當引入一個控制點P1的時候,就可以生成二階貝塞爾曲線,它是一個由二次函數描述的曲線,最多有一個頂點。

如下圖所示,P點的集合構成一個拋物線

這里解釋下上圖的綠線是如何產生的:

首先,我們已知端點P0、P2以及控制點P1,那么如何確定確定當t取某個固定值時位于貝塞爾曲線上的點P?一種簡單的方式可以通過貝塞爾曲線的公式,算出P的x和y坐標。但如何通過幾何畫法來計算出來呢?

根據貝塞爾曲線的定義,首先P0A/P0P1 = t,P1B/P1P2 = t,這樣我們可以分別確定點A與點B。然后連接AB,取AP/AB = t,那么P點就是貝塞爾曲線上的點了。

三階貝塞爾曲線:

三階貝塞爾曲線可以用一個三次函數描述,最多擁有兩個拐點。用來做兩點之間的曲線連接已經夠用了。我們來看下它的直觀形式:

一般參數公式:

給定點P0、P1、…、Pn,其貝塞爾曲線即:

公式說明

1.開始于P0并結束于Pn的曲線,即所謂的端點插值法屬性。

2.曲線是直線的充分必要條件是所有的控制點都位在曲線上。同樣的,貝塞爾曲線是直線的充分必要條件是控制點共線。

3.曲線的起始點(結束點)相切于貝塞爾多邊形的第一節(最后一節)。

4.一條曲線可在任意點切割成兩條或任意多條子曲線,每一條子曲線仍是貝塞爾曲線。

5.一些看似簡單的曲線(如圓)無法以貝塞爾曲線精確的描述,或分段成貝塞爾曲線(雖然當每個內部控制點對單位圓上的外部控制點水平或垂直的的距離為時,分成四段的貝茲曲線,可以小于千分之一的最大半徑誤差近似于圓)。

6.位于固定偏移量的曲線(來自給定的貝塞爾曲線),又稱作偏移曲線(假平行于原來的曲線,如兩條鐵軌之間的偏移)無法以貝茲曲線精確的形成(某些瑣屑實例除外)。無論如何,現存的啟發法通常可為實際用途中給出近似值

已知P0、P1...PN如何確定貝塞爾曲線上的點呢?

如上圖所示,存在頂點00,05;控制點01,02,03,04;實際上這是一條5階貝塞爾曲線。

? ? ? ?首先我們將點00到05連接起來,這樣它會有5條邊,這些邊用棕色表示。

? ? ? ?針對邊00-01,我們取一個點10,使得該點將邊00-01分成比例為t和1-t的兩部分。針對每條邊我們都取一個這樣的點。然后將這一系列點再次連接起來,這次會有4條邊,在上圖用墨綠色表示。注意到我們這樣操作之后,邊會比前面少一條。

? ? ? ?重復上面的操作,直到只有一條邊。在上圖中用綠色表示,我們取到點50,該點將這條邊分成t與(1-t)的兩部分。點50就是最終在貝塞爾曲線上的點。上述操作可以用如下公式表示:

可以查看原文更詳細的解釋

回到我們最初的問題

? ? ? ? 我們已經了解關于貝塞爾曲線的公式以及幾何畫法,但是要如何來解決我們用曲線來連接各個頂點的問題呢?

? ? ? ?前面已經提到,對于兩個點之間我們可以使用三階貝塞爾曲線來連接,這樣通過多段貝塞爾曲線相連,就可以得到我們想要的曲線。而三階貝塞爾曲線需要兩個控制點來確定,很顯然貝塞爾曲線不一定通過控制點,但是肯定通過端點。所以給定的頂點只能做端點,那問題就變成了如何計算所需要的控制點?

首先要保證曲線在頂點處連續,就要求左邊曲線在頂點處的切線和右邊曲線在頂點處的切線一致。即函數的左導數等于右導數。

根據前面的公式說明3 ?曲線的起始點(結束點)相切于貝塞爾多邊形的第一節(最后一節),我們知道,保持連續的必要條件是頂點和它前后的控制點在同一條直線上,而該直線就是曲線在該頂點的切線。

這里有一種思路:穿過已知點畫平滑曲線英文原文地址,這里也有一篇文章說的是用lua語言來實現的:開放的多點貝塞爾曲線實現

總結一下如下圖所示:

如上圖所示:如果需要繪制一條通過點A、B、C的曲線,我們需要計算各條用于連接的貝塞爾曲線的控制點。

以頂點B為例:

1、取AB和BC的中點E、F,并連接E、F

2、在EF上取點D,使得FD/DE = BC/AB

3、將直線EF按照矢量DB平移到通過B點,并且使得平移后的D和B點重合

4、得到E'與F'點用作貝塞爾曲線的控制點。

將上述算法應用于多邊形的各個頂點,可以計算出2*n個控制點(每個頂點對應兩個控制點)

下面是一種利用OC代碼的實現(實現還比較粗糙在獲,取控制點之后直接繪制了曲線,實際應用中應該先緩存起來等到繪制的時候再使用控制點。)

-(void) getControlPointx0:(CGFloat)x0 andy0:(CGFloat)y0

x1:(CGFloat)x1 andy1:(CGFloat)y1

x2:(CGFloat)x2 andy2:(CGFloat)y2

x3:(CGFloat)x3 andy3:(CGFloat)y3

path:(UIBezierPath*) path

{

CGFloat smooth_value =0.6;

CGFloat ctrl1_x;

CGFloat ctrl1_y;

CGFloat ctrl2_x;

CGFloat ctrl2_y;

CGFloat xc1 = (x0 + x1) /2.0;

CGFloat yc1 = (y0 + y1) /2.0;

CGFloat xc2 = (x1 + x2) /2.0;

CGFloat yc2 = (y1 + y2) /2.0;

CGFloat xc3 = (x2 + x3) /2.0;

CGFloat yc3 = (y2 + y3) /2.0;

CGFloat len1 = sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));

CGFloat len2 = sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));

CGFloat len3 = sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));

CGFloat k1 = len1 / (len1 + len2);

CGFloat k2 = len2 / (len2 + len3);

CGFloat xm1 = xc1 + (xc2 - xc1) * k1;

CGFloat ym1 = yc1 + (yc2 - yc1) * k1;

CGFloat xm2 = xc2 + (xc3 - xc2) * k2;

CGFloat ym2 = yc2 + (yc3 - yc2) * k2;

ctrl1_x = xm1 + (xc2 - xm1) * smooth_value + x1 - xm1;

ctrl1_y = ym1 + (yc2 - ym1) * smooth_value + y1 - ym1;

ctrl2_x = xm2 + (xc2 - xm2) * smooth_value + x2 - xm2;

ctrl2_y = ym2 + (yc2 - ym2) * smooth_value + y2 - ym2;

[path addCurveToPoint:CGPointMake(x2, y2) controlPoint1:CGPointMake(ctrl1_x, ctrl1_y)controlPoint2:CGPointMake(ctrl2_x, ctrl2_y)];

}

代碼中的smooth_value是一個縮放值,取值范圍為[0,1]。通過調整這個值可以控制曲線的銳度。

給定一組測試頂點如下:

CGFloat dx =20;

CGFloat x0 =0+ dx;

CGFloat y0 =0+ dx;

CGFloat x1 =80+ dx;

CGFloat y1 =120+ dx;

CGFloat x2 =150+ dx;

CGFloat y2 =200+ dx;

CGFloat x3 =200+ dx;

CGFloat y3 =50+ dx;

調用的代碼如下:

UIBezierPath* path = [[UIBezierPathalloc]init];

[pathmoveToPoint:CGPointMake(x1, y1)];

[selfgetControlPointx0:x0andy0:y0x1:x1andy1:y1x2:x2andy2:y2x3:x3andy3:y3path:path];

[selfgetControlPointx0:x1andy0:y1x1:x2andy1:y2x2:x3andy2:y3x3:x0andy3:y0path:path];

[selfgetControlPointx0:x2andy0:y2x1:x3andy1:y3x2:x0andy2:y0x3:x1andy3:y1path:path];

[selfgetControlPointx0:x3andy0:y3x1:x0andy1:y0x2:x1andy2:y1x3:x2andy3:y2path:path];

[pathstroke];

效果:

可是雖然實現了用曲線包圍多邊形,但是依然沒有實現我們的需求,用曲線連接各個頂點...

其實走到這一步已經離我們的目標很近了!只需要修改一下我們生成貝塞爾曲線的調用方式

[pathmoveToPoint:CGPointMake(x0, y0)];

[selfgetControlPointx0:0andy0:0x1:x0andy1:y0x2:x1andy2:y1x3:x2andy3:y2path:path];

[selfgetControlPointx0:x0andy0:y0x1:x1andy1:y1x2:x2andy2:y2x3:x3andy3:y3path:path];

[selfgetControlPointx0:x1andy0:y1x1:x2andy1:y2x2:x3andy2:y3x3:250andy3:0path:path];

[pathstroke];

效果如下:

這里需要注意的是要處理一下起始點和結束點。上面設置的為(0,0)和(250,0);這兩個點是原有的點集沒有的,根據需要可以適當設置,會影響到第一段和最后一段曲線的轉向。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容