zoukankan      html  css  js  c++  java
  • 碰撞检测 :Line

    目录

    引子

    Collision Detection :Rectangle 中主要介绍了矩形相关的碰撞检测,接着来看看直线的情况。

    以下示例未做兼容性检查,建议在最新的 Chrome 浏览器中查看。

    Line/Point

    这是示例页面

    线与点的碰撞检测,观察下面一张图:

    60-line-point

    从图中可以发现,当点在线上时,到两个端点的距离之和与线的长度相同。两点之间的距离,同样使用之前用到过的勾股定理。考虑到计算的精度误差,可以设置一个误差允许范围值,这样会感觉更加自然一些。

    /*
     * (x1,y1) 线的一个端点
     * (x2,y2) 线的另一个端点
     * (px,py) 检测点的坐标
     */
    function checkLinePoint({x1,y1,x2,y2,px,py}) {
      const d1 = getLen([px,py],[x2,y2]);
      const d2 = getLen([px,py],[x2,y2]);
      const lineLen = getLen([x1,y1],[x2,y2]);
      const buffer = 0.1; // 误差允许范围
      if (d1+d2 >= lineLen-buffer && d1+d2 <= lineLen+buffer) {
        return true; // 发生碰撞
      } else {
        return false; // 没有碰撞
      }
    }
    
    /*
     * 勾股定理计算两点间直线距离
     * point1 线的一个端点
     * point2 线的另一个端点
     */
    function getLen(point1,point2) {
      const [x1,y2] = point1;
      const [x2,y2] = point1;
      const minusX = x2-x1;
      const minusY = y2-y1;
      const len = Math.sqrt(minusX*minusX + minusY*minusY);
      return len;
    }
    

    Line/Circle

    这是示例页面

    直线和圆的碰撞检测,首先需要考虑直线是否位于圆内,因为有可能出现直线的长度小于圆的直径。为了检测这个,可以使用之前 Point/Circle 的检测方法,如果任意一端在内部,就直接返回 true 跳过剩下的检测。

    const isInside1 = checkPointCircle({px:x1,py:y1,cx,cy,radius});
    const isInside2 = checkPointCircle({px:x2,py:y2,cx,cy,radius});
    if (isInside1 || isInside2) {
      return true
    }
    

    接下来需要找到直线上离圆心最近的一个点,这个时候使用矢量的点积可以计算出最近点的坐标。下面是一个简单的数学推导过程。

    /**
     *
     * a 代表线的向量
     * t 系数
     * p1 直线上任意一点
     * p0 非直线上的一点
     * pt 直线上离 p0 最近的一点
     *
     * pt = p1 + t*a  // p1 和 pt 都在直线上,存在这样成立的关系系数 t
     *
     * (a.x,a.y)*(pt.x-p0.x,pt.y-p0.y) = 0 // 垂直的向量,点积为 0
     *
     * (a.x,a.y)*( (p1+t*a).x-p0.x,(p1+t*a).y-p0.y) = 0 // 带入 pt
     *
     * a.x *(p1.x + t*a.x - p0.x) + a.y *(p1.y + t*a.y - p0.y)  = 0
     * t*(a.x*a.x + a.y*a.y) = a.x*(p0.x-p1.x)+a.y*(p0.y-p1.y)
     * t = (a.x*(p0.x-p1.x)+a.y*(p0.y-p1.y)) / ((a.x*a.x + a.y*a.y))
     *
     * 得出系数 t 的值后,代入到一开始的公式中,就可以得出 pt 的坐标
     */
    

    然而得出的这个点可能存在这条线延伸的方向上,所以需要判断该点是否在所提供的线段上。这个时候可以使用前面介绍的关于 Line/Point 检测的方法。

    const isOnSegment = checkLinePoint({x1,y1,x2,y2, px:closestX,py:closestY});
    if (!isOnSegment) return false;
    

    最后计算圆心到直线上最近点的距离,与圆的半径进行比较,判断是否碰撞。下面是主要逻辑:

    /*
     * (x1,y1) 线的一个端点
     * (x2,y2) 线的另一个端点
     * (px,py) 圆心的坐标
     * radius  圆的半径
     */
    function checkLineCircle({x1,y1,x2,y2,cx,cy,radius}) {
      const isInside1 = checkPointCircle({px:x1,py:y1,cx,cy,radius});
      const isInside2 = checkPointCircle({px:x2,py:y2,cx,cy,radius});
      if (isInside1 || isInside2) {
        return true
      }
    
      const pointVectorX = x1 - x2;
      const pointVectorY = y1 - y2;
      const t = (pointVectorX*(cx - x1) + pointVectorY*(cy-y1))/(pointVectorX*pointVectorX+pointVectorY*pointVectorY);
      const closestX = x1 + t*pointVectorX;
      const closestY = y1 + t*pointVectorY;
    
      const isOnSegment = checkLinePoint({x1,y1,x2,y2, px:closestX,py:closestY});
      if (!isOnSegment) return false;
    
      const distX = closestX - cx;
      const distY = closestY - cy;
      const distance = Math.sqrt( (distX*distX) + (distY*distY) );
    
      if (distance <= radius) {
        return true; // 发生碰撞
      } else {
        return false; // 没有碰撞
      }
    }
    
    

    Line/Line

    这是示例页面

    直线与直线的碰撞检测,需要借助数学的推导:

    /**
     *
     * P1 P2 直线 1 上的两个点
     * A1 代表直线 1 的向量
     * t1 直线 1 的系数
     *
     * P3 P4 直线 2 上的两个点
     * A2 代表直线 2 的向量
     * t2 直线 2 的系数
     *
     * Pa = P1 + t1*A1
     * Pb = P3 + t2*A2
     *
     * 相交时,Pa = Pb
     * x1 + t1*(x2-x1) = x3 + t2*(x4-x3)
     * y1 + t1*(y2-y1) =y3 + t2*(y4-y3)
     *
     * 剩下就是二元一次方程求解
     * t1 = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3))/((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
     * t2 = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
     *
     */
    

    计算出两条线的系数后,如果两条线相交,就要符合条件:

    if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
      return true;
    }
    return false;
    

    下面是完整的判断方法:

    /*
     * (x1,y1) 线1的一个端点
     * (x2,y2) 线1的另一个端点
     * (x3,y3) 线2的一个端点
     * (x4,y4) 线2的另一个端点
     */
    function checkLineLine({x1,y1,x2,y2,x3,y3,x4,y4}) {
      const t1 = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
      const t2 = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
    
      if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
        return true; // 发生碰撞
      } else {
        return false; // 没有碰撞
      }
    }
    

    Line/Rectangle

    这是示例页面

    直线与矩形的碰撞检测,可以转换为直线与矩形四条边的碰撞检测,使用前面介绍的关于 Line/Line 检测的方法即可。

    60-line-rect

    /*
     * (x1,y1) 线的一个端点
     * (x2,y2) 线的另一个端点
     * (rx,ry) 矩形顶点坐标
     * rw 矩形宽度
     * rh  矩形高度
     */
    function checkLineRectangle({x1,y1,x2,y2,rx,ry,rw,rh}) {
      const isLeftCollision =   checkLineLine(x1,y1,x2,y2, x3:rx,y3:ry,x4:rx, y4:ry+rh);
      const isRightCollision =  checkLineLine(x1,y1,x2,y2, x3:rx+rw,y3:ry, x4:rx+rw,y4:ry+rh);
      const isTopCollision =    checkLineLine(x1,y1,x2,y2, x3:rx,y3:ry, x4:rx+rw,y4:ry);
      const isBottomCollision = checkLineLine(x1,y1,x2,y2, x3:rx,y3:ry+rh, x4:rx+rw,y4:ry+rh);
    
      if (isLeftCollision || isRightCollision || isTopCollision || isBottomCollision ) {
        return true; // 发生碰撞
      } else {
        return false; // 没有碰撞
      }
    }
    

    参考资料

  • 相关阅读:
    JAVA第六次作业
    20194672自动生成四则运算题第一版报告
    20194672自动生成四则运算第一版报告
    第四次博客作业--结对项目
    第9次作业--接口及接口回调
    第8次作业--继承
    软件工程第三次作业——关于软件质量保障初探
    第7次作业——访问权限、对象使用
    第6次作业--static关键字、对象
    Java输出矩形的面积和周长
  • 原文地址:https://www.cnblogs.com/thyshare/p/13665262.html
Copyright © 2011-2022 走看看