国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

這篇具有很好參考價值的文章主要介紹了曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

貝賽爾曲線(Bézier Curves)

原作:Keith Peters https://www.bit-101.com/blog/2022/11/coding-curves/

譯者:池中物王二狗(sheldon)

blog: http://cnblogs.com/willian/

源碼:github: https://github.com/willian12345/coding-curves

曲線藝術(shù)編程系列第8章 貝賽爾曲線

讓我們回到真正的曲線上來。貝賽爾曲線編程就非常有趣領(lǐng)人止不住的想探索一翻, 你可以自己深入學(xué)習(xí)它的組成以及相應(yīng)的公式。在我的視頻或我的書里面這些事我做過很多次了。 下面是我做的兩個視頻你可以先看看:

https://www.youtube.com/watch?v=dXECQRlmIaE
https://www.youtube.com/watch?v=2hL1LGMVnVM

這是 Freya Holmer的 (譯者注:此女的視頻相當(dāng)給力)

https://www.youtube.com/watch?v=aVwxzDHniEw
https://www.youtube.com/watch?v=jvPPXbo87ds

我還是僅限介紹最基礎(chǔ)的一些函數(shù)以及這些年積累的一些很酷實用技巧與經(jīng)驗。

基礎(chǔ)

貝塞爾曲線由兩個端點和一個控制點定義而成。它從一個點出發(fā)向控制點(不穿過控制點)再至另一個端點。你可以通過控制這些點中的任意改變曲線的形狀。這些曲線通常很優(yōu)美,應(yīng)用于各種各樣的設(shè)計工具,從繪制文字到繪制汽車,它是各種形狀繪制的關(guān)鍵組成部分。

二階貝塞爾曲線

兩個端點與一個控制點組成,如下:

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

控制點靠近 canvas 底部。如果你把它移動到右邊,它會影響到曲線:

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

細(xì)一點的線和那個點主要用于可視化演示控制點的位置。

三階貝塞爾曲線

三階貝塞爾曲線擁有兩個端點和兩個控制點,如圖:

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

高階貝塞爾曲線擁有更多的控制點,但花費的計算成本也相應(yīng)會變的更高。 可以看看 Freya 的相關(guān)視頻講解。

大多數(shù)繪圖程序 api 都有提供二階和三階曲線的函數(shù),但名字可能有比較大的出入。

我看過二階貝塞爾曲線的函數(shù)有被命名為:

  • curveTo
  • quadraticCurveTo

三階貝塞爾曲線被命名為:

  • curveTo
  • cubicCurveTo
  • bezierCurveTo

你得確保你使用的編程語言用的是哪一個。通常你可以參考上面列出的幾個例子,起始點用 moveTo 定義, 否則起點將會是繪圖 api 最近一次的繪制點,然后調(diào)用貝塞爾曲線函數(shù)定義控制點與結(jié)束點。你可以像下面這么做:

moveTo(100, 100)
cubicCurveTo(200, 100, 200, 500, 100, 300)
stroke()

但有些編程語言可以允許你一次設(shè)定所有點。它是作為基礎(chǔ)的內(nèi)建函數(shù),當(dāng)然我們還是會忽略具體內(nèi)建的 api 我們必須自己實現(xiàn)一遍。

貝塞爾曲線編碼

我們先從二階貝塞爾曲線開始然后轉(zhuǎn)向三階貝塞爾曲線。但在我們開始畫曲線路徑前,我們需要先另外創(chuàng)建一個基礎(chǔ)函數(shù)。它會提供貝塞爾曲線上任意點的點的位置。

二階貝塞爾曲線

有趣的一點是貝塞爾曲線基礎(chǔ)公式是一維的。為達到二維,三四,或更高階,你權(quán)需為每一維應(yīng)用公式。這里我們需要用兩個一維組合成二維,所以我們將執(zhí)行兩次。單參數(shù)公式如下:

x = (1 - t) * (1 - t) * x0 + 2 * (1 - t) * t * x1 + t * t * x2

此處,x0, x1, 和 x2 是兩個端點與控制點,t 的值范圍是 0.0 到 1.0。 它會根據(jù) t 的值返回這條貝塞爾曲線上對應(yīng) x 點。 當(dāng) t 為 0, x 等于 x0。 當(dāng) t 為 1, x 等于 x2。 當(dāng) t 在 0 和 1 之間時,x 會是是插值。

所以要創(chuàng)建一個二階貝塞爾曲線點的函數(shù)應(yīng)該像下面這樣做:

function quadBezierPoint(x0, y0, x1, y1, x2, y2) {
  x = (1 - t) * (1 - t) * x0 + 2 * (1 - t) * t * x1 + t * t * x2
  y = (1 - t) * (1 - t) * y0 + 2 * (1 - t) * t * y1 + t * t * y2
  return x, y
}

你可以這么做,如果你的編程語言支持返回多個值的話。否則你需要將返回值變成類似點對象。

注意,我們先去重一下。我們可以先提取 1-t 為 m 因子:

function quadBezierPoint(x0, y0, x1, y1, x2, y2, t) {
  m = (1 - t)
  x = m * m * x0 + 2 * m * t * x1 + t * t * x2
  y = m * m * y0 + 2 * m * t * y1 + t * t * y2
  return x, y
}

然后

function quadBezierPoint(x0, y0, x1, y1, x2, y2, t) {
  m = (1 - t)
  a = m * m
  b = 2 * m * t
  c = t * t
  x = a * x0 + b * x1 + c * x2
  y = a * y0 + b * y1 + c * y2
  return x, y
}

無它,就是更易讀。

有了它就可以用它畫二階貝塞爾曲線了。為了清晰的定義二階與三階,我把它們分別命名為 quadCurve 和 cubicCurve。

function quadCurve(x0, y0, x1, y1, x2, y2, res) {
  moveTo(x0, y0)
  for (t = res; t < 1; t += res) {
    x, y = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
    lineTo(x, y)
  }
  lineTo(x2, y2)
}

確保在 quadCurve 我們將起始點與結(jié)束點拆出來了,我們 moveTo 拆出第一個點,用 lineTo 拆出最后一個點。 函數(shù)接受一個 res 參數(shù),用于指定沿曲線上迭代多少次。我們將 t 初始值為 res 因為函數(shù)外已經(jīng)移動到第一個點了,無論 t 值是否為 0。中間的所有點根據(jù)當(dāng)前 t 繪制出線條。

當(dāng)然,你也可以創(chuàng)建一個 quadCurveTo 函數(shù)去掉函數(shù)內(nèi)前兩個參數(shù)還有 moveTo(譯者注:這里并非是讓你去掉,而是讓你自己決定是否單獨在函數(shù)外面調(diào)用)。 這取決于用戶自己是否需要指定曲線起始點(或從已有的路徑開始繪制)。以下是調(diào)用方式:

canvas(800, 800)
quadCurve(100, 100, 200, 700, 700, 300, 0.01)
stroke()

這會生成如下圖:

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

如果 res 變大一點,則會生成一個有點糙的曲線:

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

你已經(jīng)對 res 分辨率這個值有一定經(jīng)驗了。內(nèi)建的貝塞爾曲線會自動給定一個合適的 res 值。但我們自己實現(xiàn)的 quadCurve 函數(shù)內(nèi) res 值可能還是有點兒問題的。但在此處并不重要,因為它已經(jīng)能讓 quadBezierPoint 返回給我們足夠的坐標(biāo)值了,正如你所見的這樣。

我們的 quadBezierPoint 能用于實現(xiàn)動畫,而內(nèi)建函數(shù)做不到(譯者注:內(nèi)建函數(shù)只能一次性畫出路徑)。在這一節(jié), 就像之前章節(jié)我做的那樣, 我已經(jīng)假定你有或有能力實現(xiàn)無限循環(huán)的函數(shù)用于創(chuàng)建動畫了。 還是叫它 loop 函數(shù)。 我不會像之前那樣用 t 實現(xiàn) 0 到 1 繪制曲線,我將 讓 t 從 0 到 finalT , finalT 的值會一直變化。

canvas(400, 400)
x0 = 50
y0 = 50
x1 = 150
y1 = 360
x2 = 360
y2 = 150
finalT = 0
dt = 0.01
res = 0.025
 
function loop() {
  clearCanvas()
  moveTo(x0, y0)
  for (t = res; t < finalT; t += res) {
    x, y = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
    lineto(x, y)
  }
  stroke()
 
  // add to finalT
  finalT += dt
 
  // if we go past 1, turn it around
  if (finalT > 1) {
    finalT = 1
    dt = -dt
  } else if (finalT < 0) {
    // if we go past 0, turn it back
    finalT = 0
    dt = -dt
  }
}

結(jié)果應(yīng)該會像下面這樣的動畫

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

此處, for 環(huán)境內(nèi) t 是從 res 到 finalT 變化的所以不會畫出完整的曲線(除非 finalT 為 1)。然后我們給 finalT 加上 dt。 這會讓 finalT 慢慢接近 1, 這會曲線越來越完整。當(dāng) finalT 超過 1 時, 我們將它設(shè)為負(fù)值,這會讓整個過程反轉(zhuǎn)直到 finalT 變?yōu)?0, 這是我們變回出發(fā)點的方法。(譯都注:其實就是當(dāng) finalT 超過臨界點后,通過將 dt 設(shè)為 -dt 使得 finalT 一直在 1 和 0 之間來回變動)

相比于畫一條線, 我們這次做一個沿貝塞爾曲線運動的動畫!下面是代碼示例。相當(dāng)清晰明了。我只是添加了一個實心圓的邏輯放進 loop 函數(shù)內(nèi),剩下的代碼和之前一樣。

function loop() {
  clearCanvas()
 
  x, y = quadBezierPoint(x0, y0, x1, y1, x2, y2, finalT)
  circle(x, y, 10)
  fill()
 
  // no changes beyond here...
  // add to finalT
  finalT += dt
 
  // if we go past 1, turn it around
  if (finalT > 1) {
    finalT = 1
    dt = -dt
  } else if (finalT < 0) {
    // if we go past 0, turn it back
    finalT = 0
    dt = -dt
  }
}

這樣我們就得到了 finalT 的當(dāng)前值對應(yīng)點的 x, y 并在 x, y 處畫了個圓。假定你已經(jīng)有了 circle 繪制函數(shù)。你如果有需要你可以在第三章里復(fù)制一個過來。

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

在下面這個 gif 圖,是我用內(nèi)建的函數(shù)繪制的相同曲線,多來了一條細(xì)線表示運動軌道展示動畫一直在我們我們定義的標(biāo)準(zhǔn)的二階貝塞爾曲線上。

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

好的,稍作休息后讓我們進入三階貝塞爾曲線。

三階貝塞爾曲線

上面介紹的二階貝塞爾曲線都將應(yīng)用到三階上。只是公式不一樣 - 更復(fù)雜一點點。下面是一維的定義:

x = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t * x3

還有二維函數(shù)的定義:

function cubicBezierPoint(x0, y0, x1, y1, x2, y2, x3, y3, t) {
  x = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t * x3
  y = (1 - t) * (1 - t) * (1 - t) * y0 + 3 * (1 - t) * (1 - t) * t * y1 + 3 * (1 - t) * t * t * y2 + t * t * t * y3
  return x, y
}

是的看起來相當(dāng)亂,我們同樣提取出 1- t 因子出來整理一下:

function cubicBezierPoint(x0, y0, x1, y1, x2, y2, x3, y3, t) {
  m = 1 - t
  x = m * m * m * x0 + 3 * m * m * t * x1 + 3 * m * t * t * x2 + t * t * t * x3
  y = m * m * m * y0 + 3 * m * m * t * y1 + 3 * m * t * t * y2 + t * t * t * y3
  return x, y
}

好一點兒了,更進一步優(yōu)化后:

function cubicBezierPoint(x0, y0, x1, y1, x2, y2, x3, y3, t) {
  m = 1 - t
  a = m * m * m
  b = 3 * m * m * t
  c = 3 * m * t * t
  d = t * t * t
  x = a * x0 + b * x1 + c * x2 + d * x3
  y = a * y0 + b * y1 + c * y2 + d * y3
  return x, y
}

還可以!

現(xiàn)在可以創(chuàng)建 cubicCurve 三階貝塞爾曲線函數(shù)了。

function cubicCurve(x0, y0, x1, y1, x2, y2, x3, y3, res) {
  moveTo(x0, y0)
  for (t = res; t < 1; t += res) {
    x, y = cubicBezierPoint(x0, y0, x1, y1, x2, y2, x3, y3, t)
    lineTo(x, y)
  }
  lineTo(x2, y2)
}

很簡單。我想不需要更多的解釋了。

現(xiàn)在你的任務(wù)是:調(diào)整二階動畫用三階來實現(xiàn)一遍。僅僅需要加一個 x3, y3 的新坐標(biāo)點并調(diào)用這個新函數(shù)。

這些就是對貝塞爾曲線和路徑的基礎(chǔ)代碼實現(xiàn)了。但我這里還準(zhǔn)備了一些其它有用的小技巧給你。

過點畫線

那些剛開始使用貝塞爾曲來線編程的人經(jīng)常會說

這很巧妙,但我希望曲線能穿過控制點 ---- 這也是我-大約在 2000 年左右想要實現(xiàn)的功能

當(dāng)然可以實現(xiàn)了!這對二階貝塞爾曲線相當(dāng)容易實現(xiàn)。你只需要在更遠(yuǎn)的地方創(chuàng)建另一個控制點,控制曲線剛好穿過原控制點的位置。新的控制點很容易計算。以點 x0, y0, x1, y1, x2, y1 為例,那么新控制點會是:

x = x1 * 2 - x0 / 2 - x2 / 2
y = y1 * 2 - x0 / 2 - x2 / 2

現(xiàn)在我們可以創(chuàng)建地一個新函數(shù) quadCurveThrough 實現(xiàn)上面的代碼公式。下面是計算獲取新控制點并使用內(nèi)建函數(shù)實現(xiàn)貝塞爾曲線繪制。我假定你的系統(tǒng)中也有名為 quadraticCurveTo 的函數(shù),當(dāng)然也可能名字不同。

function quadCurveThrough(x0, y0, x1, y1, x2, y2) {
  xc = x1 * 2 - x0 / 2 - x2 / 2
  yc = y1 * 2 - y0 / 2 - y2 / 2
  moveTo(x0, y0)
  quadraticCurveTo(xc, yc, x2, y2)
}

下圖中紅的是我用標(biāo)準(zhǔn)的二階貝塞爾曲線畫的,藍的是用新函數(shù)畫的。并且繪制了那些控制點用于證明。

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

你下一個問題一定是如何在三階貝塞爾曲線實現(xiàn)同樣的過控制點繪制曲線。我暫時還不知道,但我會一直探索。我猜這也是一個機會,也許有人會在評論區(qū)給出答案,或直接告訴我這不可能實現(xiàn)??

分段二階貝塞爾曲線

人們通常會問的另一個問題是:

我如何繪制 N 個控制點的貝塞爾曲線(N 為 3 到 無窮)?早期這個問題也困擾著我

正如我之前提到過的,在數(shù)學(xué)上是可行的,但可以肯定的是從三階往上它相當(dāng)消耗性能。這也就是為什么你幾乎沒怎么見過四階貝塞爾曲線函數(shù)。但對于創(chuàng)建一條擁有任意控制點的平滑曲線還是非常有用的。當(dāng)然,你肯定已經(jīng)在某些繪圖軟件用過“鋼筆”這種類工具了。

在上面視頻樣條曲線(第二個作者為 Freya), 她展示了如何使用多個三階貝賽爾曲線組成長曲線(樣條曲線)

https://www.youtube.com/watch?v=jvPPXbo87ds

有時被稱為分段二階貝塞爾曲線,我將展示一點簡單的例子使用二階曲線。它的實現(xiàn)并不難且它支持任意多的控制點。我甚至?xí)槟銊?chuàng)建一個曲線閉環(huán)的版本。

這項技術(shù)在我的視頻中講過了:

https://www.youtube.com/watch?v=2hL1LGMVnVM

所以在此處我不會深入太多,僅覆蓋基礎(chǔ)部分并給你一些示例。

基本原則是在 “p0 p1” 之間畫創(chuàng)建一個中點稱為 pA。然后再創(chuàng)建一個 p1 與 p2 之間的中點設(shè)為 pB 。連接 p0 到 pA,然后使用 pA,p1 和 pB 繪制二階貝塞爾曲線。

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

然后你找到 p2 和 p3 之間的 pC 并且從 pB 繪制二階貝塞爾曲線通過控制點 p2 到達 pC

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

繼續(xù)以上步驟直到倒數(shù)第二個中間點,穿過倒數(shù)第二點,結(jié)束在最后一個中間點。最終連接最后一個中間點到最后一個結(jié)束點。

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

下面是畫出的曲線:

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

實現(xiàn)這個效果的代碼看起來有點兒棘手,但我已經(jīng)弄過好幾回了,很慶幸已經(jīng)有了像下面這樣的函數(shù)。注意,為了能傳遞大量參數(shù), 參數(shù)需要定義成某種類型的對象。無論它是類,結(jié)構(gòu),或擁有x, y 屬性的普通對象...。根據(jù)你自己使用的編程語言選擇吧。此函數(shù)使用數(shù)組存儲坐標(biāo)點。假定數(shù)組有個 length 屬性,在你的編程語言中有可能它不是一個屬性,而是一個獲取數(shù)組長度的方法。

function multiCurve(points) {
  // line from the first point to the first midpoint.
  // 連接第一個點到第一個中間點
  moveTo(points[0].x, points[0].y)
  midX = (points[0].x + points[1].x) / 2
  midY = (points[0].y + points[1].y) / 2
  lineTo(midX, midY)
 
  // loop through the points array, starting at index 1
  // and ending at the second-to-last point
  // 循環(huán)數(shù)組,index 從 1 開始至倒數(shù)第二個點結(jié)束
  // (譯者注:注意這循環(huán)內(nèi)的最開始的 p0 其實是數(shù)組中的第二個點了)
  for (i = 1; i < points.length - 1; i++) {
    // find the next two points and their midpoint
    p0 = points[i]
    p1 = points[i+1]
    midX = (p0.x + p1.x) / 2
    midY = (p0.y + p1.y) / 2
 
    // curve through next point to midpoint
    // 從下一個點開始繪制二階曲線至中間點
    quadraticCurveTo(p0.x, p0.y, midX, midY)
  }
 
  // we'll be left at the last midpoint
  // draw line to last point
  // 連接最后一個中間點到結(jié)束點。
  p = points[points.length - 1]
  lineTo(p.x, p.y)
}

方法看起來有點兒長,但我為每部分加了對應(yīng)的注釋。

如下例子中,我添加了半打(譯者注:一打是 12 個 半打 是 6 個)隨機坐標(biāo)點。我不知道你使用的編程語言中如何生成這些隨機數(shù),我假定你會有 randomPoint(xmin, ymin, xmax, ymax) 這樣的函數(shù)。(事實上我在自已函數(shù)庫中確實實現(xiàn)過這樣的函數(shù)!)一旦你有了這樣的數(shù)組,把數(shù)組傳進 multiCurve 后再調(diào)用 stroke 進行描邊渲染:

context(800, 800)
points = []
for (i = 0; i < 6; i++) {
  points.push(randomPoint(0, 0, 800, 800))
}
 
multiCurve(points)
stroke()

The glorious result:

看看這圖:

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

相當(dāng)不錯。 曲線之所以看起來是樣,是因為在生成這些隨機數(shù)的數(shù)組時也是要根據(jù)上下文環(huán)境來的

封閉曲線

最后部分將要介紹的是如何將函數(shù)改造成封閉曲線。主要是去除掉開始與結(jié)束線斷,并且將它首尾相連。

function multiLoop(points) {
  // find the first midpoint and move to it.
  // we'll keep this around for later
  // 找到最開始的那個中間點,將繪制點移至此點。
  // 先存下來后面會用到
  midX0 = (points[0].x + points[1].x) / 2
  midY0 = (points[0].y + points[1].y) / 2
  moveTo(midX0, midY0)
 
  // the for loop doesn't change
  // 循環(huán)和之前一樣不用變
  for (i = 1; i < points.length - 1; i++) {
    p0 = points[i]
    p1 = points[i+1]
    midX = (p0.x + p1.x) / 2
    midY = (p0.y + p1.y) / 2
    quadraticCurveTo(p0.x, p0.y, midX, midY)
  }
 
  // we'll be left at the last midpoint
  // find the midpoint between the last and first points
  // 找到數(shù)組首尾間的中間點
  p = points[points.length - 1]
  midX1 = (p.y + points[0].x) / 2
  midY1 = (p.y + points[0].y) / 2
 
  // curve through the last point to that new midpoint
  // 將最后一個點與首尾中間點相連
  quadraticCurveTo(p.x, p.y, midX1, midY1)
 
  // then curve through the first point to that first midpoint you saved earlier
  // 然后再將數(shù)組第一個點與早前我們保存的第一個中間點連接
  quadraticCurveTo(points[0].x, points[0].y, midX0, midY0)
}

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

我們先從第一個中間點開始,循環(huán)剩下的點,找到各自點的中間點并用二階貝塞爾曲線相連。我們最終停留在最后一個中間點。然后...

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

找到首尾間的中間點,并將剩下兩段曲線連在一起將形狀閉合。如下圖與上面一樣的設(shè)置一樣,但用 multiLoop 函數(shù)取代了之前的 multiCurve函數(shù) (隨機出的 points 數(shù)組值也不一樣)。

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

這是我最愛的函數(shù),我很樂意分享給你們。

均勻分布

最后要分享的技巧是如何在二階貝塞爾曲線中均勻地分布對象。 一個實用的例子是要把文本放到曲線上并且均勻的分布。當(dāng)然你肯定希望文本的角度也根據(jù)曲線的位置跟著旋轉(zhuǎn)相應(yīng)的角度,但這一部分超出了本篇文章的討論范圍。

乍一看很容易實現(xiàn)。你可以用 t 來分割曲線。如果你想在曲線上給 20 個對象留出空間, 每個對象占 0.05 份。 20 x 0.05 = 1.0。 搞定。讓我們試試:

canvas(800, 800)
 
x0 = 100
y0 = 700
x1 = 100
y1 = 100
x2 = 700
y2 = 400
 
moveTo(x0, y0)
quadraticCurveTo(x1, y1, x2, y2)
stroke()
 
// 20 evenly spaced t values (21 counting the end one)
// 20 個等距的 t 值(算上最后一個是21個)
for (t = 0; t <= 1; t += 0.05) {
  x, y = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
  circle(x, y, 6)
  fill()
}

Here’s what that gives us.

下面是我們得到的結(jié)果:

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

最終沒有均分。尾部間隔較大,中間間隔又比較緊。這就是貝塞爾曲線的特點。所以我們得找到方法讓這些點平均分布。

遺憾的是沒有什么簡單的方式來實現(xiàn)。我將用粗暴的方式強行實現(xiàn)它,當(dāng)然也會給你一些用于優(yōu)化它的提示。

為了在曲線上均分等距空格,直覺告訴我們需要先獲取曲線的總長度。如果長度是 200 像素, 你想要 20 個點均分, 那么每間隔 10 像素就放一個。

意外的是沒什么現(xiàn)成的公式可以在貝塞爾曲線實現(xiàn)這一效果。但我們可以在曲線上采樣一堆的點,獲取每個點之間的距離來模擬實現(xiàn)。代碼大致如下:

function quadBezLength(x0, y0, x1, y1, x2, y2, count) {
  length = 0.0
  dt = 1.0 / count
  x, y = x0, y0
  for (t = dt; t < 1; t += dt) {
    xn, yn = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
    length += distance(x, y, xn, yn)
    x, y = xn, yn
  }
  // (譯者注:這里原作者用了 '==' 打錯字了, 應(yīng)該是 '+=')
  length += distance(x, y, x2, y2)
  return length
}

變量 count 是采樣數(shù)量。采樣越多,越精準(zhǔn)。

然后 dt 是我們在曲線上循環(huán)迭代的步長。

我們追蹤每一步循環(huán)的 x, y 點,初始值是 x0, y0 。然后循環(huán)迭代得到曲線上每一個新坐標(biāo)點 xn, yn 并計算出上一次迭代點到此時新點的距離, 然后再將新值賦值給 x, y。不我打算展示如何計算兩點間的距離 ,我假定你已經(jīng)有這樣的函數(shù)了。將距離累加進 length.

最后再將算最后x2, y2 與 x, y 最后值的距離加到 length 上。然后返回 length 結(jié)果

確保你完全明白了,因為我已準(zhǔn)備在此處添加更多代碼了。

沿曲線追蹤每個點的距離非常有用。所以我們要把這些距離存進數(shù)組。這比返回整個長度有用,我們將直接返回這個數(shù)組

function quadBezLengths(x0, y0, x1, y1, x2, y2, count) {
  lengths = []
  length = 0.0
  dt = 1.0 / count
  x, y = x0, y0
  for (t = dt; t < 1; t += dt) {
    xn, yn = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
    length += distance(x, y, xn, yn)
    lengths.push(length)
    x, y = xn, yn
  }
  length == distance(x, y, x2, y2)
  lengths.push(length)
  return lengths
}

現(xiàn)在曲線的完整長度存在了最后的數(shù)組元素中,并且我們還有一堆其它長度,下面看我如何應(yīng)用:

count = 500
lengths = quadBezLengths(x0, y0, x1, y1, x2, y2, count)
length = lengths[count-1]
 
for (i = 0.0; i <= 1; i += 0.05) {
  // the length of the curve up to the next point
  // 曲線上下一個目標(biāo)點的長度
  targetLength = i * length
 
  // loop through the array until the length is higher than the target length
  // 循環(huán)數(shù)組直到 length 高于目標(biāo)長度
  for (j = 0; j < count; j++) {
    if (lengths[j] > targetLength) {
      // t is now the percentage of the way we got through the array.
      // this is the t value we need to get the next point
      // t 現(xiàn)在是數(shù)組的百分比
      // 這是下一個點的 t 值
      t = j / count
 
      // get the point and draw the next circle.
      // 獲取下一個點,并繪制圓。
      x, y = quadBezierPoint(x0, y0, x1, y1, x2, y2, t)
      circle(x, y, 6)
      fill()
      break
    }
  }
}

好的,有點兒小復(fù)雜, 讓我們過一遍代碼。我們用設(shè) count 為 500 采樣得到了長度數(shù)組,且獲取了總長度。。

就像之前一樣創(chuàng)建了一個 0.05 為步長 從 0 至 1 的循環(huán)。但并不像在畫貝塞爾曲線時使用的 t, 我們用它尋找曲線長度的百分比。意思是當(dāng)曲線長度為 500 像素且 i 為 0.5 時,我們尋找的的目標(biāo)點長度即為 250 像素。

現(xiàn)在我們用循環(huán)遍歷數(shù)組 通過 j 獲取長度值直到值大于 targetLength 結(jié)束內(nèi)循環(huán)。我們將 j / count 得到這一部分特定的長度。我們 t 傳入 quadBezierPoint 函數(shù)得到下一個點并把它繪制出來。此時我們應(yīng)該跳出內(nèi)部的循環(huán),直到完成這些再入下一步循環(huán)。

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

忘了還有最后一個點(因為循環(huán)并未超過它的長度),但我們僅需要再繪制另一個坐標(biāo)在 x2, y2 的點即可。這回相當(dāng)接近均分了。 count 值設(shè)的越高,均分的精度就會越高。如果 count 降為 100 ,我們可以看下它的效果:

曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)

現(xiàn)在,代碼中有許多錯誤。大多是優(yōu)化上的。

首先,二分查詢就比從 0 開始循環(huán)整個數(shù)組要好。

其次,我們不得不添加一大堆任意精度的點。用于在內(nèi)循環(huán)中匹配查找。取一個預(yù)定義的點太費性能了, 我們可以在這個點與前一個點之間插值。

舉個例子,假設(shè)我們的目標(biāo)長度是150,然后在 index 87 位置我們得到了長度是值是 160。 index 往回到 86 我們得到 140。現(xiàn)在我希望得到 86 與 87 中間的值。 比起用 87 / count , 或 86 / count ,我們用插值 50% 即 86.5 / count。雖然還是不太完美,但你現(xiàn)在可以將 count 設(shè)低一點,但得到結(jié)果卻依舊很好。

我將這些工作當(dāng)作練習(xí)留給你

如果你想獲取更多這類相關(guān)的技術(shù)信息和完整解釋,可以參考以下網(wǎng)站(譯者注:大致看了一眼鏈接的這篇文章,考慮后期也給翻譯出來)

http://www.planetclegg.com/projects/WarpingTextToSplines.html

總結(jié)

就到這里了,一些基礎(chǔ)知識,一些小提示,一些小技巧,下一章見...

本章 Javascript 源碼 https://github.com/willian12345/coding-curves/blob/main/examples/ch08


博客園: http://cnblogs.com/willian/
github: https://github.com/willian12345/文章來源地址http://www.zghlxwxcb.cn/news/detail-480440.html

到了這里,關(guān)于曲線藝術(shù)編程 coding curves 第八章 貝賽爾曲線(Bézier Curves)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 曲線藝術(shù)編程 coding curves 第六章 平托圖 (Pintographs)

    曲線藝術(shù)編程 coding curves 第六章 平托圖 (Pintographs)

    原作:Keith Peters https://www.bit-101.com/blog/2022/11/coding-curves/ 譯者:池中物王二狗(sheldon) blog: http://cnblogs.com/willian/ 源碼:github: https://github.com/willian12345/coding-curves 曲線藝術(shù)編程系列第 6 章 另一個可用于模擬繪制復(fù)雜曲線的物理裝置叫平托圖(Pintograph), 事實上我真的自己弄了一個。

    2024年02月08日
    瀏覽(23)
  • 曲線藝術(shù)編程 coding curves 第五章 諧波圖形(諧振圖形) HARMONOGRAPHS

    曲線藝術(shù)編程 coding curves 第五章 諧波圖形(諧振圖形) HARMONOGRAPHS

    原作:Keith Peters https://www.bit-101.com/blog/2022/11/coding-curves/ 譯者:池中物王二狗(sheldon) blog: http://cnblogs.com/willian/ 源碼:github: https://github.com/willian12345/coding-curves 曲線藝術(shù)編程系列第 5 章 這一篇幅建立在對第四章利薩茹曲線的討論之上。事實上諧波圖形并不是一類曲線,它是一

    2024年02月08日
    瀏覽(26)
  • 曲線藝術(shù)編程 coding curves 第七章 拋物線(Parabolas)

    曲線藝術(shù)編程 coding curves 第七章 拋物線(Parabolas)

    原作:Keith Peters https://www.bit-101.com/blog/2022/11/coding-curves/ 譯者:池中物王二狗(sheldon) blog: http://cnblogs.com/willian/ 源碼:github: https://github.com/willian12345/coding-curves 曲線藝術(shù)編程系列第7章 我承認(rèn)這一章腦暴時,再三考慮過是否要將拋物線包含進來。此篇覆蓋的拋物線比起之前三章

    2024年02月08日
    瀏覽(28)
  • 曲線藝術(shù)編程 coding curves 第三章 弧,圓,橢圓(ARCS, CIRCLES, ELLIPSES)

    曲線藝術(shù)編程 coding curves 第三章 弧,圓,橢圓(ARCS, CIRCLES, ELLIPSES)

    原作:Keith Peters https://www.bit-101.com/blog/2022/11/coding-curves/ 譯者:池中物王二狗(sheldon) blog: http://cnblogs.com/willian/ 源碼:github: https://github.com/willian12345/coding-curves 曲線藝術(shù)編程系列第三章 這一篇中我們將關(guān)注如何繪制圓弧,圓和橢圓。(結(jié)束前再聊聊正切相關(guān)的) 很可能你使用

    2024年02月07日
    瀏覽(25)
  • OSG三維渲染引擎編程學(xué)習(xí)之八十五:“第八章:OSG文字” 之 “8.4 文字特效實例”

    目錄 第八章 OSG文字 8.4 文字特效實例 ? ? ? 適當(dāng)?shù)奈淖中畔τ陲@示場景信息是非常重要的。在OSG中,osgText提供了向場景中添加文字的強大功能,由于有第三方插件FreeType的支撐,可完全支持TrueType字體。 ? ? ? TrueType是由AppleComputer公司和Microsoft公司聯(lián)合提出的一種新型數(shù)

    2024年02月11日
    瀏覽(20)
  • OSG三維渲染引擎編程學(xué)習(xí)之八十八:“第八章:OSG文字” 之 “8.7 osgText3D”

    目錄 ? 第八章 OSG文字 8.7 osgText3D 8.7.1 osgText3D介紹 8.7.2 osgText3D實例 ? ? ? 適當(dāng)?shù)奈淖中畔τ陲@示場景信息是非常重要的。在OSG中,osgText提供了向場景中添加文字的強大功能,由于有第三方插件FreeType的支撐,可完全支持TrueType字體。 ? ? ? TrueType是由AppleComputer公司和Micro

    2024年02月13日
    瀏覽(22)
  • Bezier Curve 貝塞爾曲線 - 在Unity中實現(xiàn)路徑編輯

    Bezier Curve 貝塞爾曲線 - 在Unity中實現(xiàn)路徑編輯

    貝塞爾曲線( Bezier Curve ),又稱貝茲曲線或貝濟埃曲線,是計算機圖形學(xué)中相當(dāng)重要的參數(shù)曲線,在我們常用的軟件如 Photo Shop 中就有貝塞爾曲線工具,本文簡單介紹貝塞爾曲線在Unity中的實現(xiàn)與應(yīng)用。 給頂點P 0 、P 1 ,只是一條兩點之間的直線,公式如下: B(t) = P 0 + (P

    2024年01月23日
    瀏覽(31)
  • 貝塞爾曲線(Bezier Curve)原理、公式推導(dǎo)及matlab代碼實現(xiàn)

    貝塞爾曲線(Bezier Curve)原理、公式推導(dǎo)及matlab代碼實現(xiàn)

    目錄 參考鏈接 定義 直觀理解 ?公式推導(dǎo) 一次貝塞爾曲線(線性公式) 二次貝塞爾曲線(二次方公式) ?三次貝塞爾曲線(三次方公式) n次貝塞爾曲線(一般參數(shù)公式) 代碼實現(xiàn) 貝塞爾曲線(Bezier Curve)原理及公式推導(dǎo)_bezier曲線-CSDN博客 貝塞爾曲線(Bezier Curve)原理、公

    2024年01月20日
    瀏覽(20)
  • 第八章:Linux信號

    第八章:Linux信號

    linux信號是OS的重要功能。 使用kill -l查看所有信號。使用信號時,可使用信號編號或它的宏。 1、Linux中信號共有61個,沒有0、32、33號信號。 2、【1,31】號信號稱為普通信號,【34,64】號信號稱為實時信號。 每個信號都有一個編號和一個宏定義名稱,這些宏定義可以在signal.h中

    2024年02月13日
    瀏覽(27)
  • 第八章 貪心

    Leetcode 455 思路一:大餅干喂給大胃口 上面的代碼一定要是 for 控制胃口,if 控制餅干,因為 for 中的 i 使固定移動的! 思路二:小餅干喂給小胃口 Leetcode 1005 Leetcode 860 三種情況: 情況一:賬單是5,直接收下。 情況二:賬單是10,消耗一個5,增加一個10 情況三: 賬單是20,

    2024年02月06日
    瀏覽(28)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包