1. 程式人生 > >【原創】《矩陣的史詩級玩法》連載十九:用基向量矩陣實現二次貝塞爾曲線到標準拋物線的轉換

【原創】《矩陣的史詩級玩法》連載十九:用基向量矩陣實現二次貝塞爾曲線到標準拋物線的轉換

在講解磚塊鋪貼的時候,我們先用基礎的旋轉縮放等變換組合出了45度地圖鋪貼的變換矩陣。然後發現針對性太強,換成別的角度就很不好算了。接著改成了用基向量進行推導的方法。

然後到二元二次方程,雖然我們可以通過旋轉的方法消滅掉xy項從而判斷出方程對應的曲線型別,但過程過於繁瑣,表示式太長,處理起來也很不方便,如果還要進行求交而不侷限於判斷型別,那麼用前面的旋轉法,演算就更加麻煩了。

既然在鋪貼的實現中,基向量矩陣優於旋轉矩陣,那麼在轉換二元二次方程的時候,是否也可以用基向量來簡化呢?

然而我們發現,二元二次方程中的係數,其幾何意義並不明顯,光判斷型別就要計算B^2-4AC,至於旋轉角度,偏移量這些,恐怕就更加複雜了。

不過,二次貝塞爾曲線的方程卻有特別明顯的幾何意義,建立起來的資料全部是直接的點座標。

既然如此,那我們不妨用基向量直接對二次貝塞爾曲線特有的方程進行轉換,反正上篇已經證明了它的本質是拋物線。

磚塊鋪貼我們用網格來構建基向量,那麼貝塞爾這種曲線我們怎樣來生成基向量呢?

實際上,座標系本身就可以看作網格,把刻度線延長即可生成。

emmm....理工科的學生應該會似曾相識吧,實驗課用到的座標紙,嘿嘿。

現在我們可以理直氣壯地拿(0,0)到(1,1)這一個正方形來構建基向量。現在這是標準方程,基向量自然直接跟著座標軸來,因此x方向的基向量為(1,0),y方向的基向量為(0,1)。

為了便於跟轉換後的圖形對照,我們把曲線以及端點和控制點連線所經過的所有網格也填上顏色,同時用字母標記上一些關鍵點。

A-H這8個點的座標,現在我們一眼都能看出來,但對映到一般的貝塞爾曲線之後呢?還能直接看得出來麼?反正我是看不出了。因此這裡需要有一些特徵性的東西來幫助我們在轉換之後仍然不會迷失方向。

O是拋物線頂點,但跟貝塞爾曲線很難匹配到這個頂點。

A和E缺乏典型的幾何特徵,最多有OA和OE都跟拋物線相切,但O點都很難明確了,因此先放一放。

B和D分別是曲線的兩個端點。

C是B和D連線的中點。

F和H的幾何特徵也不明顯。

G是控制點座標。

這裡直接給出一個結論:矩陣變換為仿射變換,平行線在變形前後仍為平行線,若兩平行線段(同一直線上的也算)長度相等,則變換後它們的長度也依然相等。

證明從略。

由於BCD幾何特徵明顯,所以,我們可以再加入AE平行於BD,FH平行於BD這兩條線索。

另外還有兩點:

1 OA為x方向的基向量,OC為y方向的基向量。

2 O是CG和拋物線和交點,也是拋物線的頂點。

下面我們嘗試做個變換吧。比如下圖這個樣子。

B,D,G直接是已知點,可以先畫出來。接著C是BD連線的中點,也可以直接畫得到。

然後,CG和貝塞爾曲線的交點對應的就是O點了,也剛好對應標準拋物線的頂點。

本來二次貝塞爾曲線和直線求交是要解二元二次方程組,但根據已有線索以及前面提到的矩陣變換的性質,O點剛好是CG的中點,因此能直接算出來是(0.75,0),已用藍字表示。

然後我們還知道,OA是x方向的基向量,它平行於CB並且長度跟CB相等。

因為我們的目標是求出基向量,所以A點座標可以不算出來,然後向量OA=向量BC。

所以向量OA=B-C=(2,2)-(0.5,1)=(1.5, 1)

而向量OC直接等於y方向的基向量C-O'=(0.5, 1)-(0.75, 0) = (-0.25, 1)

基向量求好了,但是可能有的朋友看著會懵逼,那我現在把色塊也加上,並且把變換前的影象搬出來,給大家一個直觀的形象。

跟磚塊鋪貼不一樣,座標原點上的拋物線頂點也跟著移動了。所以從標準方程到貝塞爾曲線方程,這裡要分兩步變換。

第一步是執行基向量矩陣變換,第二步是偏移(0.75, 0)。

在一週前(終於不是七八個月了)的連載十二中,我給出了基向量矩陣的推導過程,但這次我不用再不厭其煩地把那些讓別人看著頭痛的公式給搬過來再講解一遍,因為在緊接著的連載十三中我給出了程式碼,然後我也不手算了,直接交由瀏覽器來算。

PS:跟前面變換二元二次方程不同,這裡的變換不包含任何諸如xy這樣的未知數,所以完全可以讓任何非數學程式設計的高階語言來實現。所謂非數學程式語言,簡單點理解就是我想實現傳入一個表示式,然後無法直接生成計算後的表示式。比如

function sqr(n){
    return n*n;
}
    

我們沒有直接的方法讓這個函式在傳入"n-2"的時候給我返回"n^2-4n+4"

但是矩陣不存在這個問題,我們現在把程式碼搬過來就可以了。

var matrix = new Matrix();
var baseX = new Point(1.5, 1); //ex基向量
var baseY = new Point(-0.25, 1); //ey基向量
matrix.a = baseX.x;	
matrix.b = baseX.y;
matrix.c = baseY.x;
matrix.d = baseY.y;
MatrixUtil.translate(matrix, 0.75, 0);
console.log(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);

將輸出如下結果(其實這麼簡單的,肉眼也能看得出來):

1.5 1 -0.25 1 0.75 0

我們先把曲線的點代入下,這條曲線的3個點分別為(-1,0),(1,-1)和(2, 2),故方程為:

接著我們知道旋轉的套路是這樣寫的:

然後矩陣乘法的套路是這樣寫的:

按旋轉的格式來就是這樣:

於是我們代入到引數方程中,得到

然後矩陣的6個係數,前面輸出過了,我們代進去:

然後解一下關於X,Y的二元一次方程組(怎麼不用逆矩陣?因為t是未知數,不好通過程式碼實現,手算的話就無所謂了):

下式-上式,得到:

求X也用類似的方法:

然後發現

臥槽,還真的剛好是標準拋物線的方程誒!

至此,我們基本可以認為,這個基向量轉換矩陣是正確的!有興趣的童鞋可以自行把寫死的數字換成字母再推導一次。

從標準拋物線到二次貝塞爾曲線,使用的是上述程式碼的矩陣,而如果要反過來,從二次貝塞爾曲線轉換回標準的拋物線,那就是前面所提到的矩陣的逆矩陣了。