[Tutorial][数学向]从零开始的MC特效(七 | 矩阵)
目录:
- 一、导读
- 二、矩阵介绍
- 三、2维向量旋转
- 四、3维向量旋转
一、导读
- 本教程需要读者有一定的空间想象能力
- 本教程使用的 PaperSpigot1.15.2-R0.1-SNAPSHOT 核心
- 在阅读之前请确保你具有 线性代数 和 Java基础 的知识
- 本教程会使用 ParticleLib 中所使用的 Matrix 作为工具
二、矩阵介绍
(一) 定义
由 个数排成如下 行 列的一个表格
称为是一个 矩阵, 当 时, 矩阵 称为 阶矩阵或 阶方阵
(二) 运算法则
为了方便, 以下内容我们会尽可能的使用方阵进行解释
和运算
设有矩阵 和矩阵 , 且这两个矩阵并且行列个数相等, 即可进行和运算
则结果矩阵 为
你可能发现了, 和运算即为将对应行列进行相加减即可
乘积
这部分的内容你可能会看不明白,我建议你可以看看这个视频
设矩阵 为 行 列的矩阵, 矩阵 为 行 列的矩阵
则 为 矩阵是一个 行 列的矩阵
举个例子:
则结果矩阵 为
请牢记: 由于乘积运算会改变行列个数, 因此不满足我们熟知的乘法交换律
并且我们可以知道, 在矩阵当中, 向量左乘和右乘的得到的结果不是相同的
转置
像这样, 将矩阵的行列互换, 即为向量的转置, 并且记为
三、2维向量的变换(Transformation)
讲了这么多,我们如果不经历实操的话, 那么可能你明天就会忘记掉这些知识, 让我们以一种全新的方式来理解矩阵, 并且我希望你带着这样的一个问题,在矩阵乘法时, 在图像上是发生了怎样的一个变换?
首先我们在二维的平面上取一点 ,并且我们建立一个向量, 我们称之为 吧, 在这里我将这个向量丢入矩阵当中
HINT:请注意, 矩阵的书写可以为上方的中括号, 也可以为 这样的小括号
案例1: 将向量扩大一倍
那么我想有一个问题, 我能不能通过矩阵计算的方式, 将该向量 扩大一倍 使其落在 , 答案是可以的
让我们建立两个基向量
我们将这两个基向量扩大到原来的两倍, 也就是乘以2, 因此我们可以得到这两个向量这样的变换
之后我们将这两个基向量丢进一个矩阵 当中
如果我们将 乘 , 我们看看可以得到什么
根据矩阵乘积计算我们可以得到
我们惊喜的发现, 我们成功的将原来的向量 放大了一倍
难道是巧合吗? 我们可以试试将 (1,1) 改成任何的数, 不难发现这其实是一种通法
我们重新回顾一下上面的内容
- 首先我们选取了向量 让他变成了一个矩阵
- 我们建立了两个基向量, 将其放大一倍, 并且我们按顺序将其丢进一个矩阵 里
- 我们用矩阵T乘以矩阵 ,结果得到了上面的内容
因此我们可以认为, 首先我们将这个变换Transformation给定义好, 把他弄得像一个函数一样, 不管代入什么向量矩阵, 我们都可以将其做相同的操作,抽象一下即可得到
所以我们可以知道,矩阵其实是一种已变换后的位置的记号罢了, 所有没变化之前的向量都可以通过这个记号进行变化
案例2: 逆时针旋转45°
我们再来引入另外一个案例, 我们想定义一个矩阵, 所有向量通过这个矩阵后, 都可以逆时针旋转90°
我们可以这样来定义这个矩阵, 首先我们将基向量定在 (1,0) 和 (0,1) 想象一下它逆时针旋转90°后是怎样的情形
如果你对上述操作有疑问不妨可以回顾参数方程中圆的方程或者可以看看这个教程
并且请关注一下 第一列的向量 和 第二列的向量
我们定义一个向量 坐落在 这个点
因此可以算出它旋转之后得到的点
MC中的实现
为了减少本篇代码量, 与造福全人类的伟大使命, 我这里直接调用 ParticleLib 当中 Matrix 的代码, 你可以直接将该复制到你的项目当中
首先我们可以看到在同文件夹下有一个叫做Matrixs的类, 这个类装载着一些我写的一些预设的矩阵
让我们定义一个放大两倍的矩阵
// 定义一个2行2列的单位矩阵
Matrix T = Matrixs.eyes(2, 2).multiply(2);
// 给定一个 (1, 0, 1) 的向量 由于是
Vector v = new Vector(1, 0, 1);
// 将矩阵作用至向量上, 得到结果
Vector result = T.applyVector(v);
累积矩阵效果
如果我想做这样的一个矩阵, 先扩大2倍, 再逆时针旋转90°是怎样的呢
// 定义一个2行2列的单位矩阵, 并放大两倍的单位矩阵
Matrix T = Matrixs.eyes(2, 2).multiply(2);
// 定义一个旋转90°的矩阵
Matrix rot = Matrixs.rotate2D(90);
// 得到旋转后的矩阵
Matrix newT = rot.multiply(T); // 注意这里, 实际调用时我写成了右乘, 因此不能直接调用 T.multiply(rot)
T.prettyPrinting();
System.out.println("=====");
rot.prettyPrinting();
System.out.println("=====");
newT.prettyPrinting();
请注意上方 multiply
的部分, 写的时候写成了右乘, 如果你觉得别扭你可以自己改源码, 这些都是允许的
如果上面的代码改成 T.multiply(rot)
意思则是, 先旋转90°, 再进行扩大
输出结果
[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]
上方的那两个极小极小的数是由于精度缺失而导致的, 大家可以看成 0 即可, 在实际使用当中, 这个很小的数不会有较大的影响
[2.0, 0.0]
[0.0, 2.0]
=====
[0, 1.0]
[-1.0, 0]
=====
[0, 2.0]
[-2.0, 0]
四、3维向量的变换(Transformation)
其实3维向量的变换就是在 阶的方阵, 变成 阶的方阵
我们来看一个实际案例
Vector vector = player.getLocation().getDirection();
// 一个处于1~2之间随机的值
double random = new Random().nextDouble() + 1;
// 原点选取在一个玩家眼前向前推动一点点的距离
Arc arc = new Arc(player.getEyeLocation().add(player.getLocation().getDirection().multiply(random)))
.setStartAngle(0) // 最开始的旋转角度
.setAngle(180) // 旋转角度
.setRadius(2) // 半径
.setStep(10D); // 步进单位
arc.addMatrix(Matrixs.rotateAroundZAxis(30)) // 增加围绕Z轴旋转30°
.addMatrix(Matrixs.rotateAroundYAxis(-player.getLocation().getYaw())); // 增加围绕Y轴旋转关于玩家视角的变换, 这样就会使得特效一直在玩家眼前
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