关于javascript:碰撞检测-Line

40次阅读

共计 3925 个字符,预计需要花费 10 分钟才能阅读完成。

引子

在 Collision Detection:Rectangle 中次要介绍了矩形相干的碰撞检测,接着来看看直线的状况。

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

  • Origin
  • My GitHub

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 检测的办法即可。


/*

* (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; // 没有碰撞}

}

参考资料

  • LINE/POINT
  • LINE/CIRCLE
  • LINE/LINE
  • LINE/RECTANGLE
  • How to find a point on a line closest to another given point?

正文完
 0