简体   繁体   English

碰撞检测:分离轴定理-圆与多边形

[英]Collision detection: Separating Axis Theorem - Circle versus Polygon

I've been trying to implement collision detection between circles and polygons based on Randy Gaul's C++ Impulse Engine , following the code pretty closely, but the algorithm never returns true. 我一直在尝试根据Randy Gaul的C ++ Impulse Engine ,在圆和多边形之间实现碰撞检测,并严格遵循代码,但是算法永远不会返回true。

Here's the JSFiddle . 这是JSFiddle (the bodies are rendered using the HTML5 Canvas API for convenience) (为方便起见,使用HTML5 Canvas API渲染了主体)

A snippet of the code (just collision detection): 代码片段(仅冲突检测):

const circPoly = (a, b) => {
  let data = {},
    center = a.pos;
  data.contacts = [];
  center = b.mat.clone().trans().mult(center.clone().sub(b.pos));
  let sep = -Number.MAX_VALUE,
    faceNorm = 0;
  for (let i = 0; i < b.verts2.length; ++i) {
    let sep2 = b.norms[i].dot(center.clone().sub(b.verts2[i]));
    if (sep2 > a.radius) return data;
    if (sep2 > sep) { sep = sep2; faceNorm = i; }
  }
  let v1 = b.verts2[faceNorm],
    v2 = b.verts2[faceNorm + 1 < b.verts2.length ? faceNorm + 1 : 0];
  if (sep < 0.0001) {
    data.depth = a.radius;
    data.norm = b.mat.clone().mult(b.norms[faceNorm]).neg();
    data.contacts[0] = data.norm.clone().vmult(a.pos.clone().sadd(a.radius));
    return data;
  }
  let dot1 = center.clone().sub(v1).dot(v2.clone().sub(v1)),
    dot2 = center.clone().sub(v2).dot(v1.clone().sub(v2));
  data.depth = a.radius - sep;
  if (dot1 <= 0) {
    if (center.dist2(v1) > a.radius * a.radius) return data;
    let norm = v1.clone().sub(center);
    norm = b.mat.clone().mult(norm);
    norm.norm();
    data.norm = norm;
    v1 = b.mat.clone().mult(v1.clone().add(b.pos));
    data.contacts[0] = v1;
  } else if (dot2 <= 0) {
    if (center.dist2(v2) > a.radius * a.radius) return data;
    let norm = v2.clone().sub(center);
    norm = b.mat.clone().mult(norm);
    norm.norm();
    data.norm = norm;
    v2 = b.mat.clone().mult(v2.clone().add(b.pos));
    data.contacts[0] = v2;
  } else {
    let norm = b.norms[faceNorm];
    if (center.clone().sub(v1).dot(norm) > a.radius) return data;
    norm = b.mat.clone().mult(norm);
    data.norm = norm.clone().neg();
    data.contacts[0] = data.norm.clone().vmult(a.pos.clone().sadd(a.radius));
  }
  return data;
};

Note that b.verts2 refers to the polygon's vertices in real world coordinates. 请注意, b.verts2指的是现实世界坐标中多边形的顶点。

I know for a fact that there are no problems with the Vector class but as I don't exactly have very much experience with transformation matrices, that class could be the root of these errors, although the code for it is pretty much entirely derived from the Impulse Engine as well, so it should work. 我知道,Vector类没有问题,但是由于我对转换矩阵的经验不多,所以该类可能是这些错误的根源,尽管其代码几乎完全源自以及脉冲引擎,因此它应该可以工作。 As mentioned before, the algorithm always returns false, even when a collision really has occurred. 如前所述,即使确实发生冲突,该算法也始终返回false。 What am I doing wrong here? 我在这里做错了什么? I tried taking out the early returns, but that just returns weird results like contact points with negative coordinates which obviously is not quite correct. 我尝试取出早期收益,但是那只会返回奇怪的结果,例如带有负坐标的接触点,这显然不太正确。

EDIT: Modified my vector class's perpendicular function to work the same way as the Impulse Engine's (both ways are right, but I think one is clockwise and the other one counterclockwise -- I also modified my vertices to reflect the counterclockwise-ness). 编辑:修改了矢量类的垂直函数,使其以与Impulse Engine相同的方式工作(两种方法都是正确的,但我认为一个是顺时针方向,另一个是逆时针方向-我也修改了我的顶点以反映逆时针方向)。 Unfortunately, it still fails the test. 不幸的是,它仍然无法通过测试。

https://jsfiddle.net/khanfused/tv359kgL/4/ https://jsfiddle.net/khanfused/tv359kgL/4/

Well the are many problems and I really dont understand what you are trying to do as it seem overly complex. 好吧,有很多问题,我真的不明白您要做什么,因为它看起来过于复杂。 Eg why does matrix have trans ??? 例如为什么矩阵有trans ??? and why are you using the Y up screen as the coordinate system for the transform??? 为什么将Y up屏幕用作变换的坐标系??? (rhetorical) (修辞)

In the first loop. 在第一个循环中。

  • The first is that you are testing the distance of the normal vectors of each vert, should be testing the vert position. 首先是您正在测试每个顶点的法线向量的距离,应该在测试顶点的位置。
  • Also you are finding the distance using the vec.dot function that returns the square of the distance. 您还可以使用vec.dot函数找到距离,该函数返回距离的平方。 But you test for the radius, you should be testing for if(sep2 < radius * radius) 但是您要测试半径,应该测试if(sep2 < radius * radius)
  • And you have the comparison the wrong way around you should be testing if less than radius squared (not greater than) 并且您以错误的方式进行比较,应该测试是否小于半径平方(不大于)
  • Then when you do detect a vert within the radius you return the data object but forget to put the vert that was found inside the circle on the data.contacts array. 然后,当您确实检测到半径内的data.contacts您将返回数据对象,但忘记将在圆内找到的data.contacts放在data.contacts数组上。
  • I am not sure what the intention of keeping the index of the most distant vect is but then the rest of the function make zero sense to me???? 我不确定保留最远的vect的索引的目的是什么,但是该函数的其余部分对我来说意义为零? :( and I have tried to understand it. :(而且我试图理解它。

All you need to do is 您需要做的就是

A check if any verts on the poly are closer than radius, if so then you have a intercept (or is completely inside) 检查多边形上的任何顶点是否比半径更近,如果是,则您有截距(或完全位于内部)

Then you need to check the distance of each line segment 然后您需要检查每个线段的距离

Can be done for each line segment with the following if you dont need the intercepts (or below that if you need intercepts) only use one or the other. 如果不需要截线,则可以对每个线段进行以下操作(如果需要截线,则可以使用以下方法):仅使用一个或另一个。

// circle is a point {x:?,y:?}
// radius = is the you know what
// p1,p2 are the start and end points of a line
        checkLineCircle = function(circle,radius,p1,p2){
            var v1 = {};
            var v2 = {};
            var v3 = {};
            var u;
            // get dist to end of line
            v2.x = circle.x - p1.x;
            v2.y = circle.y - p1.y;
            // check if end points are inside the circle
            if( Math.min(
                    Math.hypot(p2.x - circle.x, p2.y - circle.y),
                    Math.hypot(v2.x, v2.y)
                ) <= radius){
                return true;
            }
            // get the line as a vector
            v1.x = p2.x - p1.x;
            v1.y = p2.y - p1.y;
            // get the unit distance of the closest point on the line
            u = (v2.x * v1.x + v2.y * v1.y)/(v1.y * v1.y + v1.x * v1.x);
            // is this on the line segment
            if(u >= 0 && u <= 1){
                v3.x = v1.x * u;  // get the point on the line segment
                v3.y = v1.y * u;
                // get the distance to that point and return true or false depending on the 
                // it being inside the circle
                return (Math.hypot(v3.y - v2.y, v3.x - v2.x) <= radius);
            }
            return false; // no intercept
      }

Do that for each line.To save time transform the circle center to the polygon local, rather than transform each point on the poly. 为节省时间,可将圆心变换到多边形局部,而不是变换多边形上的每个点。

If you need the points of intercept then use the following function 如果需要截取点,请使用以下功能

// p1,p2 are the start and end points of a line
 // returns an array empty if no points found or one or two points depending on the number of intercepts found
 // If two points found the first point in the array is the point closest to the line start (p1)
 function circleLineIntercept(circle,radius,p1,p2){
        var v1 = {};
        var v2 = {};
        var ret = [];
        var u1,u2,b,c,d;
        // line as vector
        v1.x = p2.x - p1.x;
        v1.y = p2.y - p1.y;
        // vector to circle center
        v2.x = p1.x - circle.x;
        v2.y = p1.y - circle.y;
        // dot of line and circle
        b = (v1.x * v2.x + v1.y * v2.y) * -2;
        // length of line squared * 2
        c = 2 * (v1.x * v1.x + v1.y * v1.y);
        // some math to solve the two triangles made by the intercept points, the circle center and the perpendicular line to the line.
        d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - radius * radius));
        // will give a NaN if no solution
        if(isNaN(d)){ // no intercept
            return ret;
        }
        // get the unit distance of each intercept to the line
        u1 = (b - d) / c;
        u2 = (b + d) / c;

        // check the intercept is on the line segment
        if(u1 <= 1 && u1 >= 0){  
            ret.push({x:line.p1.x + v1.x * u1, y : line.p1.y + v1.y * u1 });
        }
        // check the intercept is on the line segment
        if(u2 <= 1 && u2 >= 0){  
            ret.push({x:line.p1.x + v1.x * u2, y : line.p1.y + v1.y * u2});
        }
        return ret;
    }

I will leave it up to you to do the polygon iteration. 我将由您决定进行多边形迭代。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM