一个极其隐秘只有...吧 关注:2,375贴子:11,089
  • 16回复贴,共1

Minecraft里的射线与方块碰撞检测算法

只看楼主收藏回复

觉得挺有用的就自己花时间研究了下,手动做了反混淆,顺便去掉了一些无用代码
我是变弱了,但不代表你变强了!——一方通行


1楼2014-06-13 22:52回复
      // 参数:起始点,结束点,不忽略液体,忽略非固体方块
      public MovingObjectPosition rayTraceBlocks_do_do(Vec3 start, Vec3 end, boolean fluid, boolean noNonSolid)
      {
        if (!Double.isNaN(start.xCoord) && !Double.isNaN(start.yCoord) && !Double.isNaN(start.zCoord))
        {
          if (!Double.isNaN(end.xCoord) && !Double.isNaN(end.yCoord) && !Double.isNaN(end.zCoord))
          {
            // 终点方块坐标
            int intX2 = MathHelper.floor_double(end.xCoord);
            int intY2 = MathHelper.floor_double(end.yCoord);
            int intZ2 = MathHelper.floor_double(end.zCoord);
            // 起点(当前)方块坐标
            int intX1 = MathHelper.floor_double(start.xCoord);
            int intY1 = MathHelper.floor_double(start.yCoord);
            int intZ1 = MathHelper.floor_double(start.zCoord);
    // 检测起点方块
            int id = this.getBlockId(intX1, intY1, intZ1);
            int data = this.getBlockMetadata(intX1, intY1, intZ1);
            Block block = Block.blocksList[id];
    if (block != null && (!noNonSolid || block == null || block.getCollisionBoundingBoxFromPool(this, intX1, intY1, intZ1) != null) && id > 0 && block.canCollideCheck(data, fluid))
            {
              MovingObjectPosition movingobjectposition = block.collisionRayTrace(this, intX1, intY1, intZ1, start, end);
    if (movingobjectposition != null)
              {
                return movingobjectposition;
              }
            }
    id = 200; // 最多检测201个方块
    while (id-- >= 0)
            {
              if (Double.isNaN(start.xCoord) || Double.isNaN(start.yCoord) || Double.isNaN(start.zCoord))
              {
                return null;
              }
              // 检测了终点方块
              if (intX1 == intX2 && intY1 == intY2 && intZ1 == intZ2)
              {
                return null;
              }
    // 起点(当前)方块和终点方块XYZ不同(向某方向选了候选方块)
              boolean Xchanged = true;
              boolean Ychanged = true;
              boolean Zchanged = true;
              // 各方向候选方块坐标
              double newX;
              double newY;
              double newZ;
    // 尝试向X方向选候选方块
              if (intX2 > intX1)
              {
                newX = (double)intX1 + 1.0D;
              }
              else if (intX2 < intX1)
              {
                newX = (double)intX1;
              }
              else
              {
                Xchanged = false;
              }
    if (intY2 > intY1)
              {
                newY = (double)intY1 + 1.0D;
              }
              else if (intY2 < intY1)
              {
                newY = (double)intY1;
              }
              else
              {
                Ychanged = false;
              }
    if (intZ2 > intZ1)
              {
                newZ = (double)intZ1 + 1.0D;
              }
              else if (intZ2 < intZ1)
              {
                newZ = (double)intZ1;
              }
              else
              {
                Zchanged = false;
              }
    // 各方向候选方块离起点(当前)有多近,初始化为很大的数
              double XScale = 999.0D;
              double YScale = 999.0D;
              double ZScale = 999.0D;
              double dX = end.xCoord - start.xCoord;
              double dY = end.yCoord - start.yCoord;
              double dZ = end.zCoord - start.zCoord;
    // 向X方向选了候选方块
              if (Xchanged)
              {
                XScale = (newX - start.xCoord) / dX;
              }
    if (Ychanged)
              {
                YScale = (newY - start.yCoord) / dY;
              }
    if (Zchanged)
              {
                ZScale = (newZ - start.zCoord) / dZ;
              }
    // 最终选了哪个方向的候选方块
              byte direction;
    // 选出候选方块中离起点(当前)最近的,更新起点、要检测的方块坐标
              if (XScale < YScale && XScale < ZScale)
              {
                if (intX2 > intX1)
                {
                  direction = 4;
                }
                else
                {
                  direction = 5;
                }
    start.xCoord = newX;
                start.yCoord += dY * XScale;
                start.zCoord += dZ * XScale;
              }
              else if (YScale < ZScale)
              {
                if (intY2 > intY1)
                {
                  direction = 0;
                }
                else
                {
                  direction = 1;
                }
    start.xCoord += dX * YScale;
                start.yCoord = newY;
                start.zCoord += dZ * YScale;
              }
              else
              {
                if (intZ2 > intZ1)
                {
                  direction = 2;
                }
                else
                {
                  direction = 3;
                }
    start.xCoord += dX * ZScale;
                start.yCoord += dY * ZScale;
                start.zCoord = newZ;
              }
    intX1 = MathHelper.floor_double(start.xCoord);
    if (direction == 5) // X-方向
              {
                // MC以方块内各轴最小坐标为方块坐标,这里得到的是X上最大坐标所以要-1
                --intX1;
              }
    intY1 = MathHelper.floor_double(start.yCoord);
    if (direction == 1) // Y-方向
              {
                --intY1;
              }
    intZ1 = MathHelper.floor_double(start.zCoord);
    if (direction == 3) // Z-方向
              {
                --intZ1;
              }
    // 检测新起点方块
              int id2 = this.getBlockId(intX1, intY1, intZ1);
              int data2 = this.getBlockMetadata(intX1, intY1, intZ1);
              Block block2 = Block.blocksList[id2];
    if ((!noNonSolid || block2 == null || block2.getCollisionBoundingBoxFromPool(this, intX1, intY1, intZ1) != null) && id2 > 0 && block2.canCollideCheck(data2, fluid))
              {
                MovingObjectPosition movingobjectposition1 = block2.collisionRayTrace(this, intX1, intY1, intZ1, start, end);
    if (movingobjectposition1 != null)
                {
                  return movingobjectposition1;
                }
              }
            }
    return null;
          }
          else
          {
            return null;
          }
        }
        else
        {
          return null;
        }
      }
    命运这种东西是能简单改变的!——前原圭一


    3楼2014-06-13 22:56
    回复
        // start到end与方块碰撞检测,参数:世界,方块坐标,开始点,结束点
        /**
         * Ray traces through the blocks collision from start vector to end vector returning a ray trace hit. Args: world,
         * x, y, z, startVec, endVec
         */
        public MovingObjectPosition collisionRayTrace(World world, int X, int Y, int Z, Vec3 start, Vec3 end)
        {
          this.setBlockBoundsBasedOnState(world, X, Y, Z);
          // 以方块为原点
          start = start.addVector((double)(-X), (double)(-Y), (double)(-Z));
          end = end.addVector((double)(-X), (double)(-Y), (double)(-Z));
          // 计算start到end与方块各平面交点
          Vec3 YZ1 = start.getIntermediateWithXValue(end, this.minX);
          Vec3 YZ2 = start.getIntermediateWithXValue(end, this.maxX);
          Vec3 XZ1 = start.getIntermediateWithYValue(end, this.minY);
          Vec3 XZ2 = start.getIntermediateWithYValue(end, this.maxY);
          Vec3 XY1 = start.getIntermediateWithZValue(end, this.minZ);
          Vec3 XY2 = start.getIntermediateWithZValue(end, this.maxZ);
      // 交点不在碰撞盒内
          if (!this.isVecInsideYZBounds(YZ1))
          {
            YZ1 = null;
          }
      if (!this.isVecInsideYZBounds(YZ2))
          {
            YZ2 = null;
          }
      if (!this.isVecInsideXZBounds(XZ1))
          {
            XZ1 = null;
          }
      if (!this.isVecInsideXZBounds(XZ2))
          {
            XZ2 = null;
          }
      if (!this.isVecInsideXYBounds(XY1))
          {
            XY1 = null;
          }
      if (!this.isVecInsideXYBounds(XY2))
          {
            XY2 = null;
          }
      Vec3 hitPoint = null;
      // 选出最近的碰撞点
          if (YZ1 != null && (hitPoint == null || start.squareDistanceTo(YZ1) < start.squareDistanceTo(hitPoint)))
          {
            hitPoint = YZ1;
          }
      if (YZ2 != null && (hitPoint == null || start.squareDistanceTo(YZ2) < start.squareDistanceTo(hitPoint)))
          {
            hitPoint = YZ2;
          }
      if (XZ1 != null && (hitPoint == null || start.squareDistanceTo(XZ1) < start.squareDistanceTo(hitPoint)))
          {
            hitPoint = XZ1;
          }
      if (XZ2 != null && (hitPoint == null || start.squareDistanceTo(XZ2) < start.squareDistanceTo(hitPoint)))
          {
            hitPoint = XZ2;
          }
      if (XY1 != null && (hitPoint == null || start.squareDistanceTo(XY1) < start.squareDistanceTo(hitPoint)))
          {
            hitPoint = XY1;
          }
      if (XY2 != null && (hitPoint == null || start.squareDistanceTo(XY2) < start.squareDistanceTo(hitPoint)))
          {
            hitPoint = XY2;
          }
      // 不碰撞
          if (hitPoint == null)
          {
            return null;
          }
          else
          {
            // start到end都在碰撞盒内为-1
            byte direction = -1;
      // 与哪个面碰撞
            if (hitPoint == YZ1)
            {
              direction = 4;
            }
      if (hitPoint == YZ2)
            {
              direction = 5;
            }
      if (hitPoint == XZ1)
            {
              direction = 0;
            }
      if (hitPoint == XZ2)
            {
              direction = 1;
            }
      if (hitPoint == XY1)
            {
              direction = 2;
            }
      if (hitPoint == XY2)
            {
              direction = 3;
            }
      return new MovingObjectPosition(X, Y, Z, direction, hitPoint.addVector((double)X, (double)Y, (double)Z));
          }
        }
      老头们宣布开战, 但是战斗和死亡的都是年轻人。——赫伯特·胡夫


      4楼2014-06-13 23:00
      回复
          // 取该点到end点之间与某YZ平面交点
          /**
           * Returns a new vector with x value equal to the second parameter, along the line between this vector and the
           * passed in vector, or null if not possible.
           */
          public Vec3 getIntermediateWithXValue(Vec3 end, double X)
          {
            double dX = end.xCoord - this.xCoord;
            double dY = end.yCoord - this.yCoord;
            double dZ = end.zCoord - this.zCoord;
        // 与YZ平面平行
            if (dX * dX < 1.0000000116860974E-7D)
            {
              return null;
            }
            else
            {
              double scale = (X - this.xCoord) / dX;
              //   交点在该点和end点之间
              return scale >= 0.0D && scale <= 1.0D ? this.myVec3LocalPool.getVecFromPool(this.xCoord + dX * scale, this.yCoord + dY * scale, this.zCoord + dZ * scale) : null;
            }
          }
        要成为一名出色的爱国者,就必须要成为他国人民的敌人,真是悲剧!——伏尔泰


        5楼2014-06-13 23:02
        回复
          举个2D的例子,可以推广到3D
          起始点是(0.5, 1.75),结束点是(1.75, 0.75)
          intXY2 = 1, 0(方块3)
          intXY1 = 0, 1(方块1)
          开始时检测起点方块(0, 1)(方块1)
          --循环---------------------
          向X方向和Y方向选候选方块:
          newX = 0 + 1 = 1(代表A点X坐标)
          newY = 1(代表B点Y坐标)
          dX = 1.75 - 0.5 = 1.25
          dY = 0.75 - 1.75 = -1
          XScale = (1 - 0.5) / 1.25 = 0.4
          YScale = (1 - 1.75) / -1 = 0.75
          选出最近的(X方向)
          新起点 = (1, 1.25) (A点)
          intXY1 = 1, 1 (方块2)
          检测(1, 1)(方块2)
          ---------------------------
          intX1 == intX2,不向X方向选候选方块
          newY = 1(代表B点Y坐标)
          dX = 1.75 - 1 = 0.75
          dY = 0.75 - 1.25 = -0.5
          XScale = 999 (很大的数)
          YScale = (1 - 1.25) / -0.5 = 0.5
          选出最近的(Y方向)
          新起点 = (1.375, 1) (B点)
          intXY1 = 1, 1 - 1 = 0 (方块3)
          检测(1, 0)(方块3)
          ---------------------------
          intXY1 == intXY2,检测了终点方块,结束算法
          魔装少女就是本大爷!——相川步


          通过百度相册上传6楼2014-06-13 23:42
          回复
            我喜欢


            IP属地:北京7楼2014-11-11 11:48
            回复
              写得很好!
              吧主啥时候有空,简略分析一下minecraft的框架。。。
              我看着看着看不动了。代码量太大了,不知道怎么继续了


              IP属地:北京8楼2015-02-02 14:17
              收起回复
                抱歉楼主,挖一下坟,请问这个算法具体在什么地方有过应用?


                IP属地:广东9楼2015-12-20 18:35
                收起回复
                  挖坟问一下怎么扒出这源码的


                  IP属地:江苏10楼2016-07-18 17:15
                  收起回复
                    写mod可以,命令方块可用?


                    IP属地:广西来自Android客户端11楼2016-08-05 09:44
                    收起回复
                      好厉害..


                      IP属地:江苏来自Android客户端12楼2016-08-06 20:50
                      回复