[Tutorial][數(shù)學(xué)向]從零開始的MC特效(七 | 矩陣)

目錄:

  • 一、導(dǎo)讀
  • 二、矩陣介紹
  • 三、2維向量旋轉(zhuǎn)
  • 四、3維向量旋轉(zhuǎn)

一、導(dǎo)讀

  • 本教程需要讀者有一定的空間想象能力
  • 本教程使用的 PaperSpigot1.15.2-R0.1-SNAPSHOT 核心
  • 在閱讀之前請確保你具有 線性代數(shù)Java基礎(chǔ) 的知識(shí)
  • 本教程會(huì)使用 ParticleLib 中所使用的 Matrix 作為工具

二、矩陣介紹

(一) 定義

m \times n 個(gè)數(shù)排成如下 mn 列的一個(gè)表格
A=\begin{bmatrix} {a_{11}}&{a_{12}}&{\cdots}&{a_{1n}}\\\\ {a_{21}}&{a_{22}}&{\cdots}&{a_{2n}}\\\\ {\vdots}&{\vdots}&{\ddots}&{\vdots}\\\\ {a_{m1}}&{a_{m2}}&{\cdots}&{a_{mn}}\\\\ \end{bmatrix}
稱為是一個(gè) m \times n 矩陣, 當(dāng) m = n 時(shí), 矩陣 A 稱為 n 階矩陣或 n 階方陣

(二) 運(yùn)算法則

為了方便, 以下內(nèi)容我們會(huì)盡可能的使用方陣進(jìn)行解釋

和運(yùn)算

設(shè)有矩陣 A 和矩陣 B , 且這兩個(gè)矩陣并且行列個(gè)數(shù)相等, 即可進(jìn)行和運(yùn)算
A=\begin{bmatrix} {1}\\\\ {3}\\\\ \end{bmatrix}B=\begin{bmatrix} {1}\\\\ {0}\\\\ \end{bmatrix}
則結(jié)果矩陣 C
C=\begin{bmatrix} {2}\\\\ {3}\\\\ \end{bmatrix}
你可能發(fā)現(xiàn)了, 和運(yùn)算即為將對應(yīng)行列進(jìn)行相加減即可

乘積

這部分的內(nèi)容你可能會(huì)看不明白,我建議你可以看看這個(gè)視頻

設(shè)矩陣 Amn 列的矩陣, 矩陣 Bns 列的矩陣
ABC 矩陣是一個(gè) ms 列的矩陣

矩陣乘法

舉個(gè)例子:
A=\begin{bmatrix} {1}&{-3}\\\\ {2}&{4}\\\\ \end{bmatrix}B=\begin{bmatrix} {5}\\\\ {7}\\\\ \end{bmatrix}
則結(jié)果矩陣 C
C=\begin{bmatrix} {1\times5 + -3\times7}\\\\ {2\times5 + 4\times7}\\\\ \end{bmatrix}=\begin{bmatrix} {-16}\\\\ {38}\\\\ \end{bmatrix}
請牢記: AB \neq BA 由于乘積運(yùn)算會(huì)改變行列個(gè)數(shù), 因此不滿足我們熟知的乘法交換律
并且我們可以知道, 在矩陣當(dāng)中, 向量左乘和右乘的得到的結(jié)果不是相同的

轉(zhuǎn)置

像這樣, 將矩陣的行列互換, 即為向量的轉(zhuǎn)置, 并且記為 A^T
A=\begin{bmatrix} {5}\\\\ {2}\\\\ \end{bmatrix}A^T=\begin{bmatrix} {5}&{2} \end{bmatrix}

三、2維向量的變換(Transformation)

講了這么多,我們?nèi)绻唤?jīng)歷實(shí)操的話, 那么可能你明天就會(huì)忘記掉這些知識(shí), 讓我們以一種全新的方式來理解矩陣, 并且我希望你帶著這樣的一個(gè)問題,在矩陣乘法時(shí), 在圖像上是發(fā)生了怎樣的一個(gè)變換?

首先我們在二維的XY平面上取一點(diǎn) (1, 1) ,并且我們建立一個(gè)向量, 我們稱之為 v 吧, 在這里我將這個(gè)向量丟入矩陣當(dāng)中
v=\begin{pmatrix} {1}\\\\ {1} \end{pmatrix}
HINT:請注意, 矩陣的書寫可以為上方的中括號(hào), 也可以為 v 這樣的小括號(hào)

案例1: 將向量擴(kuò)大一倍

那么我想有一個(gè)問題, 我能不能通過矩陣計(jì)算的方式, 將該向量 v 擴(kuò)大一倍 使其落在 (2, 2), 答案是可以的
讓我們建立兩個(gè)基向量
\hat{i}=\begin{pmatrix} {1}\\\\ {0} \end{pmatrix}\hat{j}=\begin{pmatrix} {0}\\\\ {1} \end{pmatrix}
我們將這兩個(gè)基向量擴(kuò)大到原來的兩倍, 也就是乘以2, 因此我們可以得到這兩個(gè)向量這樣的變換
\hat{i}=2\begin{pmatrix} {1}\\\\ {0} \end{pmatrix}=\begin{pmatrix} {2}\\\\ {0} \end{pmatrix}
\hat{j}=2\begin{pmatrix} {0}\\\\ {1} \end{pmatrix}=\begin{pmatrix} {0}\\\\ {2} \end{pmatrix}
之后我們將這兩個(gè)基向量丟進(jìn)一個(gè)矩陣 T 當(dāng)中
T=\begin{pmatrix} {2}&{0}\\\\ {0}&{2} \end{pmatrix}
如果我們將 Tv, 我們看看可以得到什么
Result=\begin{pmatrix} {2}&{0}\\\\ {0}&{2} \end{pmatrix}\begin{pmatrix} {1}\\\\ {1} \end{pmatrix}
根據(jù)矩陣乘積計(jì)算我們可以得到
Result=\begin{pmatrix} {2\times1+0\times1}\\\\ {0\times1+2\times1} \end{pmatrix}=\begin{pmatrix} {2}\\\\ {2} \end{pmatrix}
我們驚喜的發(fā)現(xiàn), 我們成功的將原來的向量 v 放大了一倍
難道是巧合嗎? 我們可以試試將 (1,1) 改成任何的數(shù), 不難發(fā)現(xiàn)這其實(shí)是一種通法

我們重新回顧一下上面的內(nèi)容

  1. 首先我們選取了向量 v 讓他變成了一個(gè)矩陣
  2. 我們建立了兩個(gè)基向量, 將其放大一倍, 并且我們按順序?qū)⑵鋪G進(jìn)一個(gè)矩陣 T
  3. 我們用矩陣T乘以矩陣 v,結(jié)果得到了上面的內(nèi)容

因此我們可以認(rèn)為, 首先我們將這個(gè)變換Transformation給定義好, 把他弄得像一個(gè)函數(shù)一樣, 不管代入什么向量矩陣, 我們都可以將其做相同的操作,抽象一下即可得到
Result=\begin{pmatrix} {2}&{0}\\\\ {0}&{2} \end{pmatrix}\begin{pmatrix} {x}\\\\ {y} \end{pmatrix}=\begin{pmatrix} {2\times x+0\times y}\\\\ {0\times x+2\times y} \end{pmatrix}=\begin{pmatrix} {2x}\\\\ {2y} \end{pmatrix}
所以我們可以知道,矩陣其實(shí)是一種已變換后的位置的記號(hào)罷了, 所有沒變化之前的向量都可以通過這個(gè)記號(hào)進(jìn)行變化

案例2: 逆時(shí)針旋轉(zhuǎn)45°

我們再來引入另外一個(gè)案例, 我們想定義一個(gè)矩陣, 所有向量通過這個(gè)矩陣后, 都可以逆時(shí)針旋轉(zhuǎn)90°

我們可以這樣來定義這個(gè)矩陣, 首先我們將基向量定在 (1,0) 和 (0,1) 想象一下它逆時(shí)針旋轉(zhuǎn)90°后是怎樣的情形
T=\begin{pmatrix} {1}&{0}\\\\ {0}&{1} \end{pmatrix}\rightarrow\begin{pmatrix} {0}&{-1}\\\\ {1}&{0} \end{pmatrix}=\begin{pmatrix} {cos(90°)}&{-sin(90°)}\\\\ {sin(90°)}&{cos(90°)} \end{pmatrix}
如果你對上述操作有疑問不妨可以回顧參數(shù)方程中圓的方程或者可以看看這個(gè)教程
\begin{equation} C(t)=\begin{cases} x=cos(t)\\\\ y=sin(t) \end{cases} \end{equation}
并且請關(guān)注一下 第一列的向量第二列的向量
\begin{bmatrix} {cos(t)}&{-sin(t)}\\\\ {sin(t)}&{cos(t)} \end{bmatrix}

我們定義一個(gè)向量 v 坐落在 (1,2) 這個(gè)點(diǎn)
因此可以算出它旋轉(zhuǎn)之后得到的點(diǎn)
Result=\begin{pmatrix} {-2}\\\\ {1} \end{pmatrix}

MC中的實(shí)現(xiàn)

為了減少本篇代碼量, 與造福全人類的偉大使命, 我這里直接調(diào)用 ParticleLib 當(dāng)中 Matrix 的代碼, 你可以直接將該復(fù)制到你的項(xiàng)目當(dāng)中

首先我們可以看到在同文件夾下有一個(gè)叫做Matrixs的類, 這個(gè)類裝載著一些我寫的一些預(yù)設(shè)的矩陣

讓我們定義一個(gè)放大兩倍的矩陣

// 定義一個(gè)2行2列的單位矩陣
Matrix T = Matrixs.eyes(2, 2).multiply(2);
// 給定一個(gè) (1, 0, 1) 的向量 由于是
Vector v = new Vector(1, 0, 1);
// 將矩陣作用至向量上, 得到結(jié)果
Vector result = T.applyVector(v);
累積矩陣效果

如果我想做這樣的一個(gè)矩陣, 先擴(kuò)大2倍, 再逆時(shí)針旋轉(zhuǎn)90°是怎樣的呢

// 定義一個(gè)2行2列的單位矩陣, 并放大兩倍的單位矩陣
Matrix T = Matrixs.eyes(2, 2).multiply(2);
// 定義一個(gè)旋轉(zhuǎn)90°的矩陣
Matrix rot = Matrixs.rotate2D(90);
// 得到旋轉(zhuǎn)后的矩陣
Matrix newT = rot.multiply(T); // 注意這里, 實(shí)際調(diào)用時(shí)我寫成了右乘, 因此不能直接調(diào)用 T.multiply(rot)
T.prettyPrinting();
System.out.println("=====");
rot.prettyPrinting();
System.out.println("=====");
newT.prettyPrinting();

請注意上方 multiply 的部分, 寫的時(shí)候?qū)懗闪擞页? 如果你覺得別扭你可以自己改源碼, 這些都是允許的
如果上面的代碼改成 T.multiply(rot) 意思則是, 先旋轉(zhuǎn)90°, 再進(jìn)行擴(kuò)大

輸出結(jié)果

[2.0, 0.0]
[0.0, 2.0]
=====
[6.123233995736766E-17, 1.0]
[-1.0, 6.123233995736766E-17]
=====
[1.2246467991473532E-16, 2.0]
[-2.0, 1.2246467991473532E-16]

上方的那兩個(gè)極小極小的數(shù)是由于精度缺失而導(dǎo)致的, 大家可以看成 0 即可, 在實(shí)際使用當(dāng)中, 這個(gè)很小的數(shù)不會(huì)有較大的影響

[2.0, 0.0]
[0.0, 2.0]
=====
[0, 1.0]
[-1.0, 0]
=====
[0, 2.0]
[-2.0, 0]

四、3維向量的變換(Transformation)

其實(shí)3維向量的變換就是在 2\times2 階的方陣, 變成 3\times3 階的方陣
我們來看一個(gè)實(shí)際案例

Vector vector = player.getLocation().getDirection();
// 一個(gè)處于1~2之間隨機(jī)的值
double random = new Random().nextDouble() + 1;
// 原點(diǎn)選取在一個(gè)玩家眼前向前推動(dòng)一點(diǎn)點(diǎn)的距離
Arc arc = new Arc(player.getEyeLocation().add(player.getLocation().getDirection().multiply(random)))
        .setStartAngle(0) // 最開始的旋轉(zhuǎn)角度
        .setAngle(180) // 旋轉(zhuǎn)角度
        .setRadius(2) // 半徑
        .setStep(10D); // 步進(jìn)單位
arc.addMatrix(Matrixs.rotateAroundZAxis(30)) // 增加圍繞Z軸旋轉(zhuǎn)30°
        .addMatrix(Matrixs.rotateAroundYAxis(-player.getLocation().getYaw())); // 增加圍繞Y軸旋轉(zhuǎn)關(guān)于玩家視角的變換, 這樣就會(huì)使得特效一直在玩家眼前
arc.setParticle(Particle.TOTEM)
        .setCount(0)
        .setOffsetX(2 * vector.getX())
        .setOffsetY(2 * vector.getY())
        .setOffsetZ(2 * vector.getZ())
        .setExtra(0.5)
        .show();

具體效果


QQ錄屏20220709123831~2.gif
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容