本帖最后由 602723113 于 2019-1-19 21:51 编辑

目录:

  • 导读
  • Yaw,Pitch,Roll与向量的旋转
  • 利用向量旋转一个圆



导读

导读 本教程需要读者有一定的空间想象能力(因为我也懒得画图了233) 本教程使用的 Spigot1.10.2-R0.1-SNAPSHOT 核心 在阅读之前请确保你具有高中数学必修4选修4-4坐标系与参数方程Java基础的知识 (没有我不会解释的)

高三时间有限,我就不解释了,请读者见谅! —— 作者 2019/1/12

<To初中生>: 如果你是初中的话,别慌,你有趋向的概念就可以读懂本教程(应该吧...)
<To高中生>: 如果你还未学到关于上面的那本书,别慌学到了再来看也行233 (雾
<To大学生>: 没什么好说的...

Yaw,Pitch,Roll与向量的旋转

这次我们来讲讲一些概念上的东西,在Minecraft中除了X, Y, Z之外Location还有两个量一个是Yaw一个是Pitch,这两个东西在学术上被称为欧拉角(飞机姿态角) 怎么理解这三个内容呢?

Yaw: 我们 水平旋转 我们的头,也就是左右转头,这就是一次Yaw转动

Pitch: 我们 上下旋转 我们的头,也就是上下点头,这就是一次Pitch转动

Roll: 这个东西在Minecraft里面没有,但是我也讲一下,大家都玩过绝地求生吧,里面的人物QE摇头时就是一次Roll转动

看不懂我说的可以看别的有图的

那么它们跟我们的向量旋转有什么关系呢?其实在Minecraft中,我们利用Yaw和Pitch就可以描述出一个Vector向量,如我们看如下的图



在Minecraft当中,Yaw为0时,它表示的就是Z轴正半轴的方向,为-90°时则表示X轴正半轴的方向

Pitch就是你的Steve上下看时的方向,看向天空时最大值是-90,看向地板时最大值是90,正视时就是0



概念的东西都讲完了,我们来谈谈向量的旋转,那么在数学上的话,平面向量的旋转是有直接的公式套用,或者利用矩阵也可以达到目的,点我看相关资料

那么通过上面的资料,我们就可以得到三个方法

  1.     /**
  2.      * 将一个向量围绕X轴旋转angle个角度
  3.      *
  4.      * @param v     向量
  5.      * @param angle 角度
  6.      * @return {@link Vector}
  7.      */
  8.     public static Vector rotateAroundAxisX(Vector v, double angle) {
  9.         angle = Math.toRadians(angle);
  10.         double y, z, cos, sin;
  11.         cos = Math.cos(angle);
  12.         sin = Math.sin(angle);
  13.         y = v.getY() * cos - v.getZ() * sin;
  14.         z = v.getY() * sin + v.getZ() * cos;
  15.         return v.setY(y).setZ(z);
  16.     }

  17.     /**
  18.      * 将一个向量围绕Y轴旋转angle个角度
  19.      *
  20.      * @param v     向量
  21.      * @param angle 角度
  22.      * @return {@link Vector}
  23.      */
  24.     public static Vector rotateAroundAxisY(Vector v, double angle) {
  25.         angle = -angle;
  26.         angle = Math.toRadians(angle);
  27.         double x, z, cos, sin;
  28.         cos = Math.cos(angle);
  29.         sin = Math.sin(angle);
  30.         x = v.getX() * cos + v.getZ() * sin;
  31.         z = v.getX() * -sin + v.getZ() * cos;
  32.         return v.setX(x).setZ(z);
  33.     }

  34.     /**
  35.      * 将一个向量围绕Z轴旋转angle个角度
  36.      *
  37.      * @param v     向量
  38.      * @param angle 角度
  39.      * @return {@link Vector}
  40.      */
  41.     public static Vector rotateAroundAxisZ(Vector v, double angle) {
  42.         angle = Math.toRadians(angle);
  43.         double x, y, cos, sin;
  44.         cos = Math.cos(angle);
  45.         sin = Math.sin(angle);
  46.         x = v.getX() * cos - v.getY() * sin;
  47.         y = v.getX() * sin + v.getY() * cos;
  48.         return v.setX(x).setY(y);
  49.     }
复制代码

那么如果我们想用Yaw和Pitch来旋转向量应该怎么做呢?这里我直接给出方法,来自开源项目EffectLib里的VectorUtils.java

  1.     /**
  2.      * This handles non-unit vectors, with yaw and pitch instead of X,Y,Z angles.
  3.      * <p>
  4.      * Thanks to SexyToad!
  5.      * <p>
  6.      * 将一个非单位向量使用yaw和pitch来代替X, Y, Z的角旋转方式
  7.      *
  8.      * @param v            向量
  9.      * @param yawDegrees   yaw的角度
  10.      * @param pitchDegrees pitch的角度
  11.      * @return
  12.      */
  13.     public static final Vector rotateVector(Vector v, float yawDegrees, float pitchDegrees) {
  14.         double yaw = Math.toRadians(-1 * (yawDegrees + 90));
  15.         double pitch = Math.toRadians(-pitchDegrees);

  16.         double cosYaw = Math.cos(yaw);
  17.         double cosPitch = Math.cos(pitch);
  18.         double sinYaw = Math.sin(yaw);
  19.         double sinPitch = Math.sin(pitch);

  20.         double initialX, initialY, initialZ;
  21.         double x, y, z;

  22.         // Z_Axis rotation (Pitch)
  23.         initialX = v.getX();
  24.         initialY = v.getY();
  25.         x = initialX * cosPitch - initialY * sinPitch;
  26.         y = initialX * sinPitch + initialY * cosPitch;

  27.         // Y_Axis rotation (Yaw)
  28.         initialZ = v.getZ();
  29.         initialX = x;
  30.         z = initialZ * cosYaw - initialX * sinYaw;
  31.         x = initialZ * sinYaw + initialX * cosYaw;

  32.         return new Vector(x, y, z);
  33.     }
复制代码

具体的证明过程我这里就不阐述了,请读者自行解决吧...

那么有了上面的基础我们就可以来做一个简单向量旋转的特效

利用向量旋转一个圆

首先我们需要做个分析 如果我们要用向量制作一个围绕Y轴的圆可以怎么做呢? 我们看下方的代码

  1. public void createACircleWithVector(Location loc) {
  2.     double radius = 1D;
  3.     // 我们直接在X轴正半轴上加一个单位, 用来制作我们的第一个向量
  4.     Vector originalVector = getVector(loc, loc.clone().add(1, 0, 0));
  5.     originalVector.multiply(radius); // 圆的半径长度
  6.     for (int degree = 0; degree < 360; degree++) {
  7.         // 我们将向量进行旋转
  8.         Vector vector = VectorUtils.rotateAroundAxisY(originalVector, degree);
  9.         loc.add(vector);
  10.         loc.getWorld().spawnParticle(Particle.FLAME, loc, 0);
  11.         loc.subtract(vector);
  12.     }
  13. }

  14. /**
  15. * 取第一个坐标到第二个坐标的向量
  16. *
  17. * @param firstLocation  坐标1
  18. * @param secondLocation 坐标2
  19. * @return {@link Vector}
  20. */
  21. public static Vector getVector(Location firstLocation, Location secondLocation) {
  22.     return secondLocation.clone().subtract(firstLocation).toVector();
  23. }
复制代码

首先我们看这行代码 Vector originalVector = getVector(loc, loc.clone().add(1, 0, 0)); 这行要怎么理解呢?我们看下方的图来理解一下


首先我们在loc的X轴上增加了一个单位也就是图中的A点,那么我们利用终点减起点来得到向量OA(看不懂的去复习MC特效二),那么这个向量OA我们将做为我们的待旋转向量

之后我们进入循环,我们假设degree是2 Vector vector = VectorUtils.rotateAroundAxisY(originalVector, 2); 那么我们调用该方法即可得到绕Y轴逆时针旋转2个角度后的向量

之后就是坐标加向量,绘制粒子,坐标减去向量保持坐标不变等一系列通法 最终的效果就是这样啦


那么我们如果想制作一个类似物品掉落时,然后物品围绕Y轴旋转的那种效果,我们又要怎么做呢?这里我们就要利用到Yaw和Pitch

首先我们这里新建一个类用于旋转圆特效的实现,并且让它继承BukkitRunnable,当然Runnable也是可以的

  1. public class RotatableCircle extends BukkitRunnable {

  2.     // 原点
  3.     private Location location;
  4.     // X轴上的单位向量
  5.     private Vector originalVector;

  6.     private float yaw = 0;
  7.     private double radius = 1D;

  8.     public RotatableCircle(Location location) {
  9.         this.location = location.clone();
  10.         // getVector() 方法是上面已经发过的一个方法,目的是构造一个向量OA
  11.         originalVector = getVector(location, location.clone().add(1, 0, 0));
  12.     }

  13.     @Override
  14.     public void run() {

  15.     }
  16.     /**
  17.      * 取第一个坐标到第二个坐标的向量
  18.      *
  19.      * @param firstLocation  坐标1
  20.      * @param secondLocation 坐标2
  21.      * @return {@link Vector}
  22.      */
  23.     public static Vector getVector(Location firstLocation, Location secondLocation) {
  24.         return secondLocation.clone().subtract(firstLocation).toVector();
  25.     }
  26. }
复制代码

从上方的代码我们先简单的需要几个基本量,待旋转向量和原点即可,之后yaw和radius分别是yaw和圆的半径

在构造函数里,我们将传入的location参数进行克隆以防原location的变动 之后我们依然做一个向量OA,当作待旋转向量

之后我们进行几何分析,看下图


首先这是一个向量OA,如果我们想让它旋转1个yaw单位可以使用以下方法

Vector vector = VectorUtils.rotateVector(originalVector, 1, 0)


那么这就是旋转一个θ yaw角,那么pitch角要怎么旋转呢?


根据上方的图我们将向量OA旋转θ个角之后得到了向量OB,那么我们就可以这么写我们的代码


  1. // 请注意第三个参数 θ 是角度
  2. Vector vector = VectorUtils.rotateVector(originalVector, 0, θ)
复制代码

那么有了上面的铺垫我们可以这么写我们的RotatableCircle

  1. public class RotatableCircle extends BukkitRunnable {

  2.     // 原点
  3.     private Location location;
  4.     // X轴上的单位向量
  5.     private Vector originalVector;

  6.     private float yaw = 0;
  7.     private double radius = 1D;

  8.     public RotatableCircle(Location location) {
  9.         this.location = location.clone();
  10.         originalVector = VectorUtils.getVector(location, location.clone().add(1, 0, 0));
  11.     }

  12.     @Override
  13.     public void run() {
  14.         // 我们假设是第一次yaw旋转, 那么是0, 所以我们先将originalVector旋转yaw个单位
  15.         Vector vectorYaw = VectorUtils.rotateVector(originalVector, yaw, 0);
  16.         // 之后我们将pitch进行 90 ~ -90 的一个循环用于将向量进行上下翻转
  17.         for (float pitch = 90; pitch > -90; pitch--) {
  18.             Vector vector = VectorUtils.rotateVector(vectorYaw, 0, pitch);
  19.             // 这样得出来的vector只有一个半圆, 那么另外一个向量我们可以通过得到相反向量来制造出
  20.             Vector reverseVector = vector.clone().multiply(-1);

  21.             // 在正方向上绘制粒子
  22.             location.add(vector);
  23.             location.getWorld().spawnParticle(Particle.FLAME, location, 0);
  24.             location.subtract(vector);

  25.             // 在反方向上绘制粒子
  26.             location.add(reverseVector);
  27.             location.getWorld().spawnParticle(Particle.FLAME, location, 0);
  28.             location.subtract(reverseVector);
  29.         }

  30.         // 将yaw设定在0~360之间进行循环
  31.         if (yaw >= 360) {
  32.             yaw = 0;
  33.         } else {
  34.             yaw++;
  35.         }
  36.     }
  37. }
复制代码

我们在代码中像下方一样调用

  1. RotatableCircle rotatableCircle = new RotatableCircle(location);
  2. rotatableCircle.runTaskTimer(你插件的主类实例, 0L, 3L);
复制代码

最终的效果




结语
高三不易,望读者谅解! —— 撰写: 一个来自普高文科的学生

[groupid=1306]Bone Studio[/groupid]