聲明:如下內容出現了一些 TeX 數學公式,它們顯示不出來,這不是個人錯 :)node
更正:段錯誤網站有個 Bug,它會自動檢測文本里是否出現了 TeX 數學公式,而後才能進行公式渲染,但檢測規則是必須出現行間公式,即python
$$ E = mc^2 $$git
這樣的公式。github
在一些與幾何模型設計相關的工做中,設計人員一般會先構造出一些型值點,而這些點每每來自於現實中的一些經驗或測量結果。爲了美觀,或者爲了便於加工,或者有些狀況下是爲了讓設計轉化爲現實以後可以知足現實中的性能需求,例如飛機、汽車、船舶等交通工具都要考慮如何在形體設計上下降流體的阻力,總之,在多數狀況下會指望曲線/曲面可以儘可能光滑地穿過這些型值點。算法
這種問題在 CAGD 中,就是曲線插值問題,即空間中存在一組點,$P_1,\cdots,P_n$,如何用一條多項式曲線光滑的穿過它們。這是計算機輔助幾何設計(CAGD)領域的最基本的問題之一。segmentfault
因爲曲線必須經過 $P_0,\cdots,P_n$,即任何一個 $P_i$,$i\in\{0,1,\cdots,n\}$ 都對曲線的整體形狀具備決定做用,所以將 $P_i$ 稱爲控制點,取其可控制曲線形狀之意。函數
控制點的數量稱爲待插值曲線的階數,即曲線的次數加 1。工具
在思考曲線插值這個問題以前,不妨先思考或回顧一下線性插值。或者不妨假設,咱們如今也只會解決線性插值問題。性能
線性插值,就是求穿過點 $P_0$ 與 $P_1$ 的一條直線。不要去回想咱們曾經學過的在直角座標系上定義的直線方程了,緣由很簡單,直線之因此是直線,並不是直角座標系決定的,而是它的內蘊決定的。網站
過 $P_0$ 與 $P_1$ 的一條直線,它的內蘊是 $P(t) = \frac{t_1 - t}{t_1 - t_0}P_0 + \frac{t - t_0}{t_1 - t_0}P_1$。爲何這樣說呢,由於不管是直線仍是曲線,它們構成的是一維空間,這裏的 $t$ 就是這個空間中任意一個點的「座標」。
若是我生活在這條直線以內,那麼我只知道我在這個空間中的任何走動只會致使 $t$ 發生變化,除此以外,我一無所知。至於我在這條直線所嵌入的那個空間內,我是 $P_0$ 仍是 $P_1$ 或者其餘什麼東西,那都與我無關。固然,若是你生活與 $P_0$ 與 $P_1$ 同一維度的空間裏,當我自認爲通過 $t = t_0$ 時,在你看來,我通過的是 $P_0$,而當我自認爲通過 $t = t_1$ 時,在你看來則是通過 $P_1$。
我甚至不知道這個空間是否是彎曲的,由於在我看來,它始終都是直的,並且只有兩個方向,除非我發現我向一個方向走啊走,有一天發現我又走回了我出發的地方,這時我能夠斷言我所在的空間是彎曲的。
如今,你在你生存的世界裏要對不共線的三個點 $P_0$、$P_1$、$P_2$ 進行曲線插值,插值的結果就是讓我生存的空間變得彎曲,然而我卻對此一無所知。當我在這個空間中走動的時候,當我通過 $t_0$、$t_1$、$t_2$ 時,我一直以爲是在一個平直的空間裏行走,而在你看來,我通過的則是 $P_0$、$P_1$、$P_2$ 。
那麼,你應當如何給我製造這樣的空間呢?很簡單,就是作三次線性插值。第一次是對 $P_0$ 與 $P_1$ 進行線性插值,結果爲 $P_{01}(t)$。第二次是對 $P_1$ 與 $P_2$ 進行線性插值,結果爲 $P_{12}(t)$。第三次是對 $P_{01}(t)$ 與 $P_{12}(t)$ 插值,結果爲 $P_{012}(t)$。 結果就是,你在你的世界裏構造了一條彎曲的 2 次光滑曲線 $P_{012}(t)$,並讓它無比精確地穿過了 $P_0$、$P_1$、$P_2$,然而在我看來,個人空間依然平直。
$P_{01}(t)$ 與 $P_{12}(t)$ 很容易理解,在你的空間裏,它是兩條相交的直線,$P_1$ 是交點。那麼,如何對 $P_{01}(t)$ 與 $P_{12}(t)$ 進行線性插值呢?顯然,在 $t_0 \le t \le t_1$ 時,你但願我位於 $P_{01}(t)$ 中,而 $t_1 \le t \le t_2$ 時,你但願我位於 $P_{12}(t)$ 中,能同時知足這些要求的對 $P_{01}(t)$ 與 $P_{12}(t)$ 的線性插值形式只有一種,即 $P_{012}(t) = \frac{t_2 - t}{t_2 - t_0}P_{01}(t) + \frac{t - t_0}{t_2 - t_0}P_{12}(t)$。
這彷佛沒什麼難理解的,在大家的世界裏,曾經有個叫歐幾里得的老先生說過,過相異兩點,能做且只能做一條直線。對於 $P_{012}(t)$ 而言,按照這位老先生說的去理解就沒錯。$P_{01}(t)$ 與 $P_{12}(t)$ 雖然是直線,可是隻要固定 $t$,它們就得老老實實地「坍縮」爲兩個點,而此時 $P_{012}(t)$ 是過這兩個點的直線上的一點。若不固定 $t$,而是讓它遞增或遞減的發生變化,那麼 $P_{012}(t)$ 就能夠造成一條曲線。所以,咱們也能夠像歐老先生那樣說話,過相交的兩條直線,經過線性插值能做且只能做一條二次曲線。
還能夠說得更普遍,過兩條相異且存在部分區域重疊的 n 次曲線,經過線性插值,能做且只能做一條 n + 1 次曲線。例如,若沿襲上文對 $P(t)$ 的標記方式,那麼對 $P_{012}(t)$ 與 $P_{123}(t)$ 這兩條相異且存在部分區域重疊的二次曲線進行線性插值,能夠獲得 $P_{0123}(t)$,它是一條三次曲線,過 $P_0$、$P_1$、$P_2$、$P_3$ 四個點。
像 $P_{01\cdots n}(t)$ 這樣的函數,在中學數學課本里稱爲多項式。如今,它的係數包含着 n + 1 個點,它就被稱爲多項式曲線了。數學家們已經證實了,過 n + 1 個點的 n 次多項式曲線不只存在,並且唯一。正是基於這一結論,在上文我纔敢胡說八道,開歐幾里得老先生的玩笑。
你看,就一直在玩線性插值的戲法,結果能變出來在你看來挺彎曲的線,而對於活在一維空裏的我看來,它仍是一條很直的線……咱們這個空間,也有個老先生,他實在是太老了,因此叫老子,他說,大直若曲,大巧若拙。此言真是不虛啊。後來,還有個叫姜太公的老先生,用直的魚鉤釣了條大魚。
有位老先生,名叫 Eric Harold Neville,是一個數學家。他提出一個算法,很簡潔地描述出上述的曲線插值過程。這個算法以他的姓氏命名,叫 Neville 算法。這位老先生,我國的人知道的彷佛並不太多。在百度上搜索「Neville 算法」,結果連前 3 頁都未能佔滿。
這個算法很簡單,用圖的形式描述起來會很直觀,不過它倒是計算機算法類教科書說不清、道不白的大名鼎鼎的動態規劃算法。以 2 次曲線插值三個點爲例,下面即是 Neville 算法的動態規劃圖:
我在 Hamal 項目中提供了這個算法的 Python 實現,見 https://github.com/liyanrui/h...
這個腳本的用法以下:
$ python3 neville --curve="foo-curve" --sample=100 --nodes=foo-nodes.asc foo.asc
foo.asc
爲控制點集文件,其中每一行是控制點的座標,例如:
1 1 0 3 2 0 4 3 0 7 2 0
foo-nodes.asc
爲控制點對應的 t 值,例如:
0 1 2 3
neville 腳本會根據 --sample
選項的值做爲採樣次數對插值曲線進行均勻採樣。例如,當 --sample
指定的值爲 10 時,neville 腳本會以 0, 1/10, 2/10, ..., 10/10 對插值曲線進行求值,從而獲得 10 個點,這些點被保存在 foo-curve.asc
文件,而它們的鏈接關係被保存在 foo-curve-graph.asc
文件。這兩份文件的名字,前綴部分皆爲 foo-curve
,由 --curve
選項給定。
可使用 hamal 腳本將插值曲線的採樣點集、採樣點之間的關係(造成一組首尾相連的線段集合的關係)以及控制點等數據轉換爲 POV-Ray 場景文件,交由 povray 進行圖形渲染。
對於上一節 neville 腳本示例的輸入數據及輸出結果,使用 hamal 腳本轉換爲 POV-Ray 場景文件及圖形渲染的命令以下:
$ python3 hamal --object="neville" \ --curve=foo-curve-graph.asc \ --control-points=foo.asc --line-width=0.015 \ --control-point-size=0.04 foo-curve.asc $ povray +P neville.pov
結果以下圖所示:
關於 hamal 腳本的其餘用法,見「hamal 指南」。
下一篇:曲線裏的時間、位移與質量