本帖最后由 结冰的离季 于 2022-4-19 20:58 编辑 
前言
硬要求
本文也发布于博客(阅读体验更好):https://www.iseason.top/mc-scope-mining
背景故事
了解需要注意的问题
逻辑复制代码P.S. 这里存在一个问题,要以玩家方向为基准还是以指针在被挖掘方块上的方向为基准
正篇


思路转换
变换矩阵


从二维谈起

三维变换矩阵

代码实现(Kotlin)复制代码
复制代码

另一个解~坐标系转换

实践


复制代码
复制代码
复制代码

复制代码复制代码
P.S. bbs 的md语法支持了个寂寞,排版好痛苦
前言
硬要求
- 了解矩阵/线性代数/计算机图形学基础(没有也没关系,我会尽量解释清楚)
- 了解Bukkit API
- 掌握一门编程语言( 推荐 java / kotlin) 本文使用kotlin语言
- 耐心
 
本文也发布于博客(阅读体验更好):https://www.iseason.top/mc-scope-mining
背景故事
了解需要注意的问题
起因是我需要实现一个范围挖掘 功能,类似于 x * y * z 这样的一个区域,相信很多人在各种mod\插件都见到过了。开始我以为这么多见的功能应该不是很难实现,但但我真正上手开发时却发现没那么简单。
- 3x3范围挖掘效果
 
逻辑
按照一般的逻辑是监听BlockBreakEvent 获取方块 block,然后:
- 判断玩家方向。比如对player用 Entity::getFacing()获取一个 BlockFace 枚举类型,然后对这个枚举类型进行判断你就可以知道玩家面向的方向,然后你就会发现它无法得知玩家向上还是向下,于是利用 LivingEntity::getEyeLocation() 获取玩家眼部位置eyeLocation,然后 使用 Location::getPitch() 方法获取玩家仰角(以实体为中心绕自身X轴旋转的角度,对仰角值进行判断。然后就可以知道玩家上下东西南北的朝向了。当然除了使用 Entity::getFacing()方法还可以使用 Location::getYaw()方法(以实体为中心绕自身Y轴旋转的角度,也叫偏航角)
 
- @EventHandler
 
-     fun onBlockBreakEvent(event: BlockBreakEvent) {
 
-         val block = event.block //方块
 
-         val player = event.player //玩家
 
-         var facing = player.facing // BlockFace ->NORTH,EAST,SOUTH,WEST
 
-         val pitch = player.eyeLocation.pitch //0表示水平朝向.90表示向下,-90表示向上
 
-         facing = if (pitch < -45.0) BlockFace.DOWN else if (pitch > 45.0) BlockFace.UP else facing
 
- }
- 如上图,是挖墙还是挖地? 本例子为挖墙
- 确定区域坐标。到这里你可以思考一下:假设我要破坏n * m * 1 的方块,比如我有个2 * 3 * 1 的区域,我要它始终面对我,需要怎么做?根据MC WIKI 中 对于[color=var(--primary-color)]世界坐标系 的说明把上面得到的6个方向转换成坐标轴上的方向,如果是对称图形,比如3 * 3 * 1,假设其面向X轴正方向,那么它的X坐标就是0 ,那么可以使用2层 循环来生成6个方块对应的坐标。然后再把这些方块的坐标移动到目标位置。
 [size=0.8]复制代码- //需要移动的坐标
 
- val blockX = 0 
 
- val blockY = 0
 
- val blockZ = 0
 
- for (y in 1..3) { // 1<=y<=3
 
-     for (z in 1..2) { // 1<=z<=2
 
-         //从世界中获取位置上的方块
 
-          player.world.getBlockAt(0 + blockX, y + blockY, z + blockZ)
 
-     }
 
- }
 
 而根据对称性可以知道它跟X轴负方向是一样的,可以不用旋转。那么只需要处理 X 、Y 、Z轴3种情况即可但如果图形不是对称的,比如2 * 2 * 1 、1 * 3 * 4 那么需要处理6种情况。如果想要支持 东西、东南等方向,则需要12种情况。可想而知,最终写出来的代码会很难看。以下为是我反编译一款范围挖掘插件的代码,进行了6次以上的判断,而且不会对形状进行旋转,算法各有好坏,其他插件的代码截图仅仅为了做对比,没有任何贬低的意思。下面这个插件好一些,利用了一些算法来减少代码量,但是本质不变
 - 破坏位置上的方块 略
- 如果你看到这里觉得这个功能已经完成,那么请你看看下面这些 3 x 3 的方块能够实现吗不是我杠,而是我想要说明的是:通过穷举的办法不完美,无法适应多种情况
 
 
 
- //需要移动的坐标
正篇
思考本篇将解决一个问题: 如何才能做到任意角度,任意形状的范围挖掘?本文不会深入解释理论知识,只谈运用
范围挖掘,其实原版就有,不过原版是在一个点上,也就是指哪挖哪。那么怎么把一个点拓展到一个区域呢?
你可以想象当玩家挖方块时,存在一个过方块中点的平面,且平面与玩家视线垂直 ,从平面上出发且与平面垂直的向量叫平面的法线/法向量 
当然范围挖掘不只是一个面,但我们可以从面出发来寻找解决问题办法。
说到点和法向量这你可能想起这个公式:
但是求这个平面对于解决问题没什么帮助,你无法由面得出想要的坐标。(没错,我踩坑过了,浪费了几小时)
以上都是我解决问题中的思考。当我发现不能解决问题时,我换了另一个思路
思路转换
在 3d 建模软件或者游戏引擎 中,对物体的平移、旋转、缩放是基本功能,那么我们的“平面”可以当做从原点经过一系列变换到玩家面前的,而构成 一个面/物体的基本单元就有点的存在,这个点就是我们需要求的。 
那么在计算机图形学中,要怎么对一个图形/物体进行变换?
答案就是 变换矩阵
变换矩阵是一种矩阵,属于线性代数知识。
数学上,一个 m x n的矩阵是一个由 m行(row)n列(column)元素排列成的矩形阵列。矩阵里的元素可以是数字、符号或数学式。点我了解详情 简单来说,矩阵的存在是为了解决数学问题,比如解线性方程组
- 矩阵通式
 
- 线性方程组
 
设缩放后的点为(Xm,Ym),则有以下2个公式
- Xm = 0.5Xn
- Ym = 0.5Yn
 
- Xm = Sx * Xn
- Ym = Sy * Yn写成矩阵(AB = C)的形式就是
 
那么如上图 矩阵 A 就是一个变换矩阵。由于它起着缩放的作用,故而又分为缩放矩阵,根据作用,还有平移矩阵,旋转矩阵等,他们都有现成的公式,我就不再推导了,直接用就行。
三维变换矩阵是用于对三维空间点进行变换的矩阵
我这里直接给结果,想知道怎么推导的可自行搜索。参考链接: https://cloud.tencent.com/developer/article/1005561
- 缩放矩阵
 
- 旋转矩阵
- 平移矩阵
 
将以上矩阵相乘即可得出一个综合变换矩阵A ( 4x4的 ),将原始坐标3个值组成的矩阵设为 B(x,y,z,1) 经过变换后的坐标设为C(X,Y,Z,1)
则  C = B X A (左乘)
你会发现我标识了左乘,那就是还有右乘。矩阵的左乘和右乘结果是不一样的。比如旋转矩阵的图,将右边2个交换位置是不能成立的。如何通俗理解矩阵左乘和右乘的区别?
代码实现(Kotlin)
写了这么多该来点实际的了
- 如果你会java但不会kotlin,这里简单说下两者的一些区别方便理解:
 - A::getXXX()方法可以直接用 a.xxx 调用,比如player.getLocation() ==> player.location ;setter也一样
- fun test() ==> public void test()
- val/var 为声明字段 会自动判断类型, val 不可二次赋值,var 可以
- fun a(a:Int = 1) ==> public void a() 和 public void a(Int a)
- for(x in 1..20) ==> for(Int x = 1; x<=20;x++) ;for(x in 1 until 20) ==> for(Int x = 1; x<20;x++)
- fun Location.test(a:Int) ==> public void test(Location loc,Int a)
- 不需要分号;
- Vectro() ==> new Vector()
 
 
- 首先得知道,许多编程语言都没有关于矩阵运算的官方库。一般都是使用2维数组来构建矩阵的。比如我之前写过一个二维变换矩阵的作业,用的是c++ 和 qt ,项目地址:https://github.com/Iseason2000/CG-Geometric-Transformation-QTKotlin同样没有矩阵相关的官方库,所以我找了一个库来用(你也可以自己实现):https://github.com/VeronicaCrippa/kotlin-matrix
- 计算变换矩阵matrixOf()方法的前两个参数分别是矩阵的 列和行数,剩下的可变参数逐行填充进矩阵
 
- /**
 
-  * 根据变换生成变换矩阵,支持平移、旋转 和缩放
 
-  */
 
- fun getTransformMatrix(
 
-     moveX: Double = 0.0,
 
-     moveY: Double = 0.0,
 
-     moveZ: Double = 0.0,
 
-     rotateX: Double = 0.0,
 
-     rotateY: Double = 0.0,
 
-     rotateZ: Double = 0.0,
 
-     scaleX: Double = 1.0,
 
-     scaleY: Double = 1.0,
 
-     scaleZ: Double = 1.0,
 
- ): Matrix<Double> {
 
-     //平移矩阵
 
-     val translationMatrix = matrixOf(
 
-         4, 4, //列和行
 
-         1.0, 0.0, 0.0, moveX,
 
-         0.0, 1.0, 0.0, moveY,
 
-         0.0, 0.0, 1.0, moveZ,
 
-         0.0, 0.0, 0.0, 1.0
 
-     )
 
-     // X 轴旋转矩阵
 
-     val rotationMatrixX = matrixOf(
 
-         4, 4,
 
-         1.0, 0.0, 0.0, 0.0,
 
-         0.0, cos(rotateX), -sin(rotateX), 0.0,
 
-         0.0, sin(rotateX), cos(rotateX), 0.0,
 
-         0.0, 0.0, 0.0, 1.0
 
-     )
 
-     // Y 轴旋转矩阵
 
-     val rotationMatrixY = matrixOf(
 
-         4, 4,
 
-         cos(rotateY), 0.0, sin(rotateY), 0.0,
 
-         0.0, 1.0, 0.0, 0.0,
 
-         -sin(rotateY), 0.0, cos(rotateY), 0.0,
 
-         0.0, 0.0, 0.0, 1.0
 
-     )
 
-     // Z 轴旋转矩阵
 
-     val rotationMatrixZ = matrixOf(
 
-         4, 4,
 
-         cos(rotateZ), -sin(rotateZ), 0.0, 0.0,
 
-         sin(rotateZ), cos(rotateZ), 0.0, 0.0,
 
-         0.0, 0.0, 1, 0.0,
 
-         0.0, 0.0, 0.0, 1.0
 
-     )
 
-     //缩放矩阵
 
-     val scalingMatrix = matrixOf(
 
-         4, 4,
 
-         scaleX, 0.0, 0.0, 0.0,
 
-         0.0, scaleY, 0.0, 0.0,
 
-         0.0, 0.0, scaleZ, 0.0,
 
-         0.0, 0.0, 0.0, 1.0
 
-     )
 
-     // dot 方法 使用了 infix 修饰。与 a.dot(b) 一样 将2个矩阵相乘,是原来是左乘,我改成了右乘
 
-     val rotationMatrix = rotationMatrixY dot rotationMatrixX dot rotationMatrixZ
 
-     val transform = translationMatrix dot rotationMatrix dot scalingMatrix
 
-     return transform
 
- }
- 化简变换矩阵为了效率可以自己提前计算合并变换矩阵复制代码- /**
 
-  * 根据变换生成变换矩阵,支持平移、旋转 和缩放
 
-  */
 
- fun getTransformMatrix(
 
-     moveX: Double = 0.0,
 
-     moveY: Double = 0.0,
 
-     moveZ: Double = 0.0,
 
-     rotateX: Double = 0.0,
 
-     rotateY: Double = 0.0,
 
-     rotateZ: Double = 0.0,
 
-     scaleX: Double = 1.0,
 
-     scaleY: Double = 1.0,
 
-     scaleZ: Double = 1.0,
 
- ): Matrix<Double> {
 
-     //本矩阵为右乘,如果你是左乘可以将矩阵转置下
 
-     return matrixOf(
 
-         4,
 
-         4,
 
-         scaleX * cos(rotateY) * cos(rotateZ),
 
-         scaleY * (sin(rotateX) * sin(rotateY) * cos(rotateZ) - cos(rotateX) * sin(rotateZ)),
 
-         scaleZ * (cos(rotateX) * sin(rotateY) * cos(rotateZ) + cos(rotateX) * sin(rotateZ)),
 
-         moveX,
 
 
-         scaleX * cos(rotateX) * sin(rotateZ),
 
-         scaleY * (sin(rotateX) * sin(rotateY) * sin(rotateZ) + cos(rotateX) * cos(rotateZ)),
 
-         scaleZ * (cos(rotateX) * sin(rotateY) * sin(rotateZ) - sin(rotateX) * cos(rotateZ)),
 
-         moveY,
 
 
-         -scaleX * sin(rotateY),
 
-         scaleY * sin(rotateX) * cos(rotateY),
 
-         scaleZ * cos(rotateX) * cos(rotateY),
 
-         moveZ,
 
 
-         0.0, 0.0, 0.0, 1.0
 
-     )
 
- }
 
 
 
- /**
- 现在需要考虑传入的参数了,我们的需求是平移和旋转。
 - 平移很简单,假设目标位置是(0,0,0) ,那么移动后的位置就是玩家挖的那个方块的位置(x,y,z),所以移动参数就是x,y,z。
- 旋转角的获取也不难。Location类提供了getYaw() 和 getPitch() 方法,分别对应Y轴、X轴的旋转。你可能会好奇:Z轴(Roll)呢?请你想一想,mc中玩家能歪头吗? 或许mod可以,但是在bukkit中roll是不存在的。也就是说玩家头部旋转方向只有Y和X轴。自然而然roll 角 为0.
- 如果你在确定了旋转角之后美滋滋地去测试,就会发现,嗯?怎么不对?这就是相对旋转的问题了,我们要的是让物体绕玩家旋转,而不是绕自己旋转,所以物体自己的旋转角应该是 - yaw。
- 复制代码- val location = target.location //玩家指向的方块的坐标
 
- // X轴旋转角(角度 转 弧度)
 
- val eyePitch = eyeLocation.pitch / 180.0 * Math.PI
 
- //Y轴旋转角(角度 转 弧度)
 
- val eyeYaw = eyeLocation.yaw / 180.0 * Math.PI
 
- //生成变换矩阵
 
- val transformMatrix =
 
-     getTransformMatrix(
 
-         location.x + 0.5,
 
-         location.y + 0.5,
 
-         location.z + 0.5,
 
-         eyePitch,
 
-         -eyeYaw
 
- )
 
 
- val location = target.location //玩家指向的方块的坐标
 
- 有了变换矩阵就可以计算坐标了,写个方法来计算坐标复制代码- /**
 
-  * 由变换矩阵求出变换后的位置
 
-  */
 
- fun Location.transform(transformMatrix: Matrix<Double>): Location {
 
-     //由原始点坐标构建一个矩阵用于计算
 
-     //this = 调用的那个Location,kotlin的 拓展函数 语法
 
-     val pointMatrix = matrixOf(
 
-         1, 4,
 
-         this.x, this.y, this.z, 1.0
 
-     )
 
-     //原点矩阵右乘变换矩阵得到变换后的坐标矩阵
 
-     val matrix = transformMatrix dot pointMatrix
 
-     return Location(this.world, matrix[0][0], matrix[1][0], matrix[2][0])
 
- }
 
 
- /**
- 最后将方法封装一下
 
 
- /**
 
-  * 在给定方块周围获取与玩家视角平行的 xyz 矩形区域内的方块
 
-  * @param target 起始方块
 
-  * @param rangeX 宽度
 
-  * @param rangeY 高度
 
-  * @param rangeZ 深度
 
-  * @return 所有方块的集合(除了 target)
 
-  */
 
- fun Player.getScopeBlocksByMatrix(target: Block, rangeX: Int, rangeY: Int, rangeZ: Int): Set<Block> {
 
-     val set = mutableSetOf<Block>()
 
-     val location = target.location
 
-     val world = location.world
 
-     // X轴旋转角(角度 转 弧度)
 
-     val eyePitch = eyeLocation.pitch / 180.0 * Math.PI
 
-     //Y轴旋转角(角度 转 弧度)
 
-     val eyeYaw = eyeLocation.yaw / 180.0 * Math.PI
 
-     //生成变换矩阵
 
-     val transformMatrix =
 
-         getTransformMatrix(
 
-             location.x + 0.5,
 
-             location.y + 0.5,
 
-             location.z + 0.5,
 
-             eyePitch,
 
-             -eyeYaw
 
-         )
 
-     val halfRangeX = rangeX / 2
 
-     val halfRangeY = rangeY / 2
 
-     //宽
 
-     for (x in -halfRangeX until rangeX - halfRangeX) {
 
-         //高
 
-         for (y in -halfRangeY until rangeY - halfRangeY) {
 
-             //深度,从被挖的方块往里
 
-             for (z in 0 until rangeZ) {
 
-                 val block = Location(world, x.toDouble(), y.toDouble(), z.toDouble()).transform(transformMatrix).block
 
-                 set.add(block)
 
-             }
 
-         }
 
-     }
 
-     set.remove(target)
 
-     return set
 
- }
- 获取了集合之后就是破坏了复制代码- private val scopeSet = mutableSetOf<Player>() //这个是类成员,用于储存玩家状态,免得引发连锁反应无限套娃
 
- @EventHandler
 
-     fun onBlockBreakEvent(event: BlockBreakEvent) {
 
-         val player = event.player
 
-         if (scopeSet.contains(player)) return
 
-         val itemInMainHand = player.equipment.itemInMainHand
 
-         if (itemInMainHand.type.isAir) return
 
-         val level = itemInMainHand.getMMOData<DoubleData>(stat)?.value ?: return
 
-         //以上属于物品判断,看不懂也没关系,我是将范围挖掘变成一个等级level 1级就是1x2 2级是2x2...
 
-         scopeSet.add(player)
 
-         //计算挖掘范围
 
-         val count = level.toInt() + 2
 
-         val rangeX = count / 2
 
-         val rangeY = count - rangeX
 
-         //获取范围内的方块
 
-         val scopeBlocks = player.getScopeBlocksByMatrix(event.block, rangeX, rangeY, 1)
 
-         val iterator = scopeBlocks.iterator()
 
-                 //另启任务,防止卡线程,每tick破坏10个方块
 
-         submit(period = 1L) {
 
-             repeat(10) {
 
-                 if (!iterator.hasNext()) {
 
-                     scopeSet.remove(player)
 
-                     cancel()
 
-                     return@submit
 
-                 }
 
-                 val block = iterator.next()
 
-                 //镐子不能挖掘的方块都不要,比如沙子
 
-                 if (!Tag.MINEABLE_PICKAXE.values.contains(block.type)) {
 
-                     return@repeat
 
-                 }
 
-                 // 这个会调用BlockBreakEvent 和 BlockDropItemEvent 非常好用,会被取消,兼容其他插件很方便
 
-                 player.breakBlock(block)
 
-             }
 
-         }
 
- }
 
 
- private val scopeSet = mutableSetOf<Player>() //这个是类成员,用于储存玩家状态,免得引发连锁反应无限套娃
- 这样功能就完成了,测试一下,非常好用。
 
不止是三种基本变换,切变、反射、投影等变换也可以通过变换矩阵来完成,利用矩阵来解决问题其实更适合粒子特效之类的显示功能
利用这个图形学的基础知识,我们便可在MC中构建一个建模引擎。来实现任意形状的显示。如果再加上时间维度,那么像MAYA一样来做动画也并非天方夜谭。
做了个旋转与缩放的动画

另一个解~坐标系转换
理论看到这可能会有人说:矩阵好难啊!有没有更简单的办法。答案是有的。
我们都知道MC只有一个世界坐标系(O-xyz 右手笛卡尔坐标系 )。但是我们完全可以创建一个新的坐标系,通过两个坐标系之间的映射关系来转换两个坐标系的点。
假设有如下2个坐标系Oxy 与 O'x'y'
则有 
- 在Oxy坐标系下,X轴的单位向量OX= (1,0) ;Y轴的单位向量OY=(0,1)
- 在O'x'y'坐标系下,X'轴的单位向量O'X'= (1,0) ;Y'轴的单位向量O'Y'=(0,1)
 
根据单位向量的性质,坐标系中任何一个点的向量都可以由位于该坐标系坐标轴上的单位向量表示,比如点(1,1) = OX+OY ;(1,2) = OX+2*OY
设 O'点在Oxy坐标系下的坐标为(Xo,yo) 则:O' = XoOX + YoOY = Xo(1,0)+Yo(0,1) = (Xo,Yo) = OO'
设P'点在O'X'Y'坐标系下的坐标为 P'(x',y') 则O'P' = (X',Y')
由向量加法: AB+BC = BC 可知 OP' = OO'+O'P' = (Xo,Yo)+(X',Y')= (Xo,Yo)+X' * O'X + Y' * O'Y'
也就是说,在已知O'点的OXY坐标与P'点的O'X'Y'坐标和O'X'Y'坐标系的两个坐标轴的单位向量的情况下,就能得到坐标系O'X'Y'到OXY的映射关系。
简单来说,知道了玩家坐标和以玩家头部为中心的坐标系3个轴向上在世界坐标系中的单位向量,就能够将以玩家为坐标系中的点映射到世界坐标系中。
玩家头部坐标很容易得到,那么只差玩家坐标系了。
在第三人称下打开F3+B 可以开启判定箱,你会发现玩家头部有条蓝色的线,这就是玩家的视线。而BukkitAPI 恰好有个 LivingEntity:;getEyeLocation().getDirection()方法,这个就是玩家视线方向的单位向量
可以知道当玩家的yaw和pitch都为0时(没有roll,默认为0),玩家朝向的是正南方,也就是世界坐标系Z轴正方向
所以Location里的getDirection()方法得到的单位向量是Z轴的。
但是BukkitAPI并没有提供玩家的另外2个轴,我们需要至少2个单位向量才能够利用正交性算出最后一个单位向量。
通过翻阅getDirection()的源码可以知道它是如何实现的
- public Vector getDirection() {
 
-         Vector vector = new Vector();
 
 
-         double rotX = this.getYaw();
 
-         double rotY = this.getPitch();
 
 
-         vector.setY(-Math.sin(Math.toRadians(rotY)));
 
 
-         double xz = Math.cos(Math.toRadians(rotY));
 
 
-         vector.setX(-xz * Math.sin(Math.toRadians(rotX)));
 
-         vector.setZ(xz * Math.cos(Math.toRadians(rotX)));
 
 
-         return vector;
 
- }
我们知道Z轴与X轴在yaw上差了90°,其他不变,所以可以求出X轴的单位向量
- /**
 
-  * 根据坐标yaw和pith值获取X方向的单位向量
 
-  * @return X方向的单位向量
 
-  */
 
- fun Location.getNormalX(): Vector {
 
-     val vector = Vector()
 
-     val rotX = yaw.toDouble()
 
-     //row =0 , pitch = 0
 
-     vector.x = cos(Math.toRadians(rotX))
 
-     vector.z = sin(Math.toRadians(rotX))
 
-     return vector
 
- }
最后我们就可以得到3个轴的单位向量了
- /**
 
-  * 获取玩家相对坐标系的3个坐标轴在世界坐标系下的单位向量
 
-  * @return 一个向量数组, index 0 为 X轴,1 为 Y轴,2 为Z轴
 
-  */
 
- fun Player.getRelativeCoordinate(): Array<Vector> {
 
-     val eyeLocation = eyeLocation
 
-     val normalX = eyeLocation.getNormalX()  // X 轴 其实就是getDirection()方法,我改了个名字
 
-     val normalZ = eyeLocation.getNormalZ()  //Z 轴
 
-     val normalY = normalX.clone().crossProduct(normalZ).multiply(-1) //叉乘得出Y轴,测试发现朝下,于是乘以-1使其朝上
 
-     return arrayOf(normalX, normalY, normalZ)
 
- }
测试下,完美
然后封装一下由相对坐标计算世界坐标的方法
- // 以自身为原点和相对坐标系获取世界坐标
 
- fun Location.getRelativeByCoordinate(
 
-     coordinate: Array<Vector>, //坐标轴的单位向量
 
-     x: Double,
 
-     y: Double,
 
-     z: Double
 
- ): Location {
 
-     return clone().apply {
 
-         add(coordinate[0].clone().multiply(x))
 
-         add(coordinate[1].clone().multiply(y))
 
-         add(coordinate[2].clone().multiply(z))
 
-     }
 
- }
最后收尾
- /**
 
-  * 由相对坐标系算法获取相对范围内的方块
 
-  * @param target 起始方块
 
-  * @param rangeX 宽度
 
-  * @param rangeY 高度
 
-  * @param rangeZ 深度
 
-  * @return 所有方块的集合(除了 target)
 
-  */
 
- fun Player.getScopeBlocksByVector(target: Block, rangeX: Int, rangeY: Int, rangeZ: Int): Set<Block> {
 
-     val set = mutableSetOf<Block>()
 
-     //玩家与准星上的方块之间的距离
 
-     val baseX = target.location.apply {
 
-         x += 0.5
 
-         y += 0.5
 
-         z += 0.5
 
-     }.distance(eyeLocation)
 
-     val halfRangeX = rangeX / 2
 
-     val halfRangeY = rangeY / 2
 
-     //获取相对坐标系
 
-     val relativeCoordinate = getRelativeCoordinate()
 
-     val eyeLocation = eyeLocation
 
-     //宽
 
-     for (x in -halfRangeX until rangeX - halfRangeX) {
 
-         //高
 
-         for (y in -halfRangeY until rangeY - halfRangeY) {
 
-             //深度,从被挖的方块往里
 
-             for (z in 0 until rangeZ) {
 
-                 //相对坐标转世界坐标并获取对应的方块
 
-                 val block = eyeLocation.getRelativeByCoordinate(
 
-                     relativeCoordinate,
 
-                     x.toDouble(),
 
-                     y.toDouble(),
 
-                     baseX + z.toDouble()
 
-                 ).block
 
-                 set.add(block)
 
-             }
 
-         }
 
-     }
 
-     set.remove(target)
 
-     return set
 
- }
后话
上述2中算法各有优缺点:
- 变换矩阵适合复杂变换,世界坐标中的适合物体变换
- 坐标系转换效率高一些(我写的代码,1W次循环中变换矩阵30ms,坐标系变换5ms),但是仅仅是坐标映射,需要其他变换还是得另做处理
 
      写这篇文章的目的不是说这个算法多么nb。范围挖掘算法只是一个引子,由实际问题引出图形学知识,为一些在图形显示、动画方面遇到问题的人指明方向。程序员不一定懂算法,懂算法也不一定会图形学,本文没有去推导、深入解释图形学知识,就是为了让不懂的人能够了解图形学有什么用,以后遇到时知道可以从哪个方向解就行。
      MC插件开发者何许多,但大部分插件都缺少创新,其中固有需求的影响,也有能力的限制,将自己局限在MC的世界中。我们不妨向其他游戏对齐,市面上有多游戏引擎,比如Unity、Unreal等,他们创造出了一个广阔的游戏世界,Minecraft又何尝不是一个游戏引擎,别的引擎能做的我们也能做。
      服务端插件的最大局限性是无法修改客户端,只能由服务器端处理,这样固然有极大的限制,但是全交给服务器端来做也未尝不是好事,比如反作弊,大部分游戏外挂的实现都是通过修改客户端的,服务端没有对客户端数据进行检查,于是导致了出现正常情况下不会出现的数据,服务端插件实现的功能全由服务端控制,客户端自然就无法轻易欺骗服务端,反作弊能力大于客户端mod。
      其实我对于图形学我也是一知半解,知识全来自我们的专业课,学的不咋地,不深入探讨也有这个因素,但这并不影响我在MC中推广它,有错误请多多指教。
我一开始想的是直接把目标方块周围方块全部移除不就行了,看完大开眼界 还好线代知识还没还回去勉强能理解一点
还好线代知识还没还回去勉强能理解一点
我的评价是:不如转成极坐标系来运算
有没有可能,我只是说可能,获取eyeLocation,然后和玩家头部相邻六个方块的distance取min,就能知道视野朝向了 
 本帖最后由 结冰的离季 于 2022-4-14 12:26 编辑 
首先输入点都是笛卡尔坐标系下的,用极坐标也离不开换算成笛卡尔坐标系的点,而且对于在极坐标构建点集来组成想要的形状,太麻烦了。本质上算法跟第二种办法是一样的,只是概念不一样。
而且本文章是讨论图形学在mc中的应用,不是说这样做一定是最好的
子德 发表于 2022-4-14 08:34
我的评价是:不如转成极坐标系来运算
首先输入点都是笛卡尔坐标系下的,用极坐标也离不开换算成笛卡尔坐标系的点,而且对于在极坐标构建点集来组成想要的形状,太麻烦了。本质上算法跟第二种办法是一样的,只是概念不一样。
而且本文章是讨论图形学在mc中的应用,不是说这样做一定是最好的
看起来很不错,不过学不会
《写 一 个 插 件 使 我 拓 展 了 许 多 数 学 知 识》
四姑一,大佬啊我看了半天我才想起来我不会编程awa
对于新手来说这个还是挺方便的,大部分的问题上面都有,不得不说解决的很多多多的 问题题
,666666666666666
旋转矩阵原来是四阶的呀,我之前一直用三维的,结果写的特别复杂……(还是直接用叉乘吧 )
)
Ph-苯 发表于 2022-4-19 20:03
旋转矩阵原来是四阶的呀,我之前一直用三维的,结果写的特别复杂……(还是直接用叉乘吧) ...
因为三维坐标的位移需要4阶的,所以统一用四阶了
结冰的离季 发表于 2022-4-19 20:24
因为三维坐标的位移需要4阶的,所以统一用四阶了
三维坐标的位移需要四阶?不是只有旋转才要四阶吗?
Ph-苯 发表于 2022-4-20 21:57
三维坐标的位移需要四阶?不是只有旋转才要四阶吗?
就我所知就缩放不用
奈何自己没文化  一句wc走天下
大佬啊我看了半天我才想起来我不会编程awa