【问题标题】:How to fix circle and rectangle overlap in collision response?如何修复碰撞响应中的圆形和矩形重叠?
【发布时间】:2013-09-13 08:11:43
【问题描述】:

由于在数字世界中几乎不会发生真正的碰撞,因此我们总会遇到“碰撞”的圆圈与矩形重叠的情况。

圆与矩形完美碰撞没有重叠的情况下如何放回?

假设矩形停止(零速度)并且轴对齐。

我会用a posteriori 方法(二维)解决这个问题。

简而言之,我必须为 t 求解这个方程

地点:

  • 是一个回答问题的数字:多少帧之前 碰撞完美发生?

  • 是圆的半径。

  • 是圆心

  • 是它的速度。

  • 和 是返回 x 和 y 坐标的函数 圆和矩形碰撞的点(当圆是 在位置,即与矩形完美碰撞的位置)。

最近解决了一个similar problem的圆碰撞问题,但是现在不知道函数A和B的规律。

【问题讨论】:

    标签: java collision-detection physics collision game-physics


    【解决方案1】:

    多年来一直盯着这个问题,一直没有想出完美的解决方案,我终于做到了!

    这几乎是一个简单的算法,不需要循环和近似。

    这是它在更高级别上的工作方式:

    1. 如果从当前点到未来点的路径穿过该平面,则计算与每一侧平面的相交时间。
    2. 检查每一边的象限是否有单边交叉点,返回交叉点。
    3. 确定圆相撞的角。
    4. 求解当前点、拐角和相交中心(远离拐角的半径)之间的三角形。
    5. 计算时间、法线和交点中心。

    现在是血淋淋的细节!

    函数的输入是边界(它有一个左、上、右、下)和一个当前点(开始)和一个未来点(结束)。

    输出是一个名为 Intersection 的类,它有 x、y、time、nx 和 ny。

    • {x, y} 是相交时的圆心。
    • 时间是一个从 0 到 1 的值,其中 0 表示开始,1 表示结束
    • {nx, ny} 为法线,用于反映速度以确定圆的新速度

    我们从经常使用的缓存变量开始:

    float L = bounds.left;
    float T = bounds.top;
    float R = bounds.right;
    float B = bounds.bottom;
    float dx = end.x - start.x;
    float dy = end.y - start.y;
    

    并计算与每一面的平面相交的时间(如果起点和终点之间的向量经过该平面):

    float ltime = Float.MAX_VALUE;
    float rtime = Float.MAX_VALUE;
    float ttime = Float.MAX_VALUE;
    float btime = Float.MAX_VALUE;
    
    if (start.x - radius < L && end.x + radius > L) {
       ltime = ((L - radius) - start.x) / dx;
    }
    if (start.x + radius > R && end.x - radius < R) {
       rtime = (start.x - (R + radius)) / -dx;
    }
    if (start.y - radius < T && end.y + radius > T) {
       ttime = ((T - radius) - start.y) / dy;
    }
    if (start.y + radius > B && end.y - radius < B) {
       btime = (start.y - (B + radius)) / -dy;
    }
    

    现在我们尝试看看它是否严格来说是一个侧面交叉点(而不是拐角)。如果碰撞点位于侧面,则返回交点:

    if (ltime >= 0.0f && ltime <= 1.0f) {
       float ly = dy * ltime + start.y;
       if (ly >= T && ly <= B) {
          return new Intersection( dx * ltime + start.x, ly, ltime, -1, 0 );
       }
    }
    else if (rtime >= 0.0f && rtime <= 1.0f) {
       float ry = dy * rtime + start.y;
       if (ry >= T && ry <= B) {
          return new Intersection( dx * rtime + start.x, ry, rtime, 1, 0 );
       }
    }
    
    if (ttime >= 0.0f && ttime <= 1.0f) {
       float tx = dx * ttime + start.x;
       if (tx >= L && tx <= R) {
          return new Intersection( tx, dy * ttime + start.y, ttime, 0, -1 );
       }
    }
    else if (btime >= 0.0f && btime <= 1.0f) {
       float bx = dx * btime + start.x;
       if (bx >= L && bx <= R) {
          return new Intersection( bx, dy * btime + start.y, btime, 0, 1 );
       }
    }
    

    我们已经走到了这一步,所以我们知道要么没有交叉路口,要么它与一个角落相撞。我们需要确定角点:

    float cornerX = Float.MAX_VALUE;
    float cornerY = Float.MAX_VALUE;
    
    if (ltime != Float.MAX_VALUE) {
       cornerX = L;
    } else if (rtime != Float.MAX_VALUE) {
       cornerX = R;
    }
    
    if (ttime != Float.MAX_VALUE) {
       cornerY = T;
    } else if (btime != Float.MAX_VALUE) {
       cornerY = B;
    }
    
    // Account for the times where we don't pass over a side but we do hit it's corner
    if (cornerX != Float.MAX_VALUE && cornerY == Float.MAX_VALUE) {
       cornerY = (dy > 0.0f ? B : T);
    }
    
    if (cornerY != Float.MAX_VALUE && cornerX == Float.MAX_VALUE) {
       cornerX = (dx > 0.0f ? R : L);
    }
    

    现在我们有足够的信息来求解三角形。这使用距离公式,找到两个向量之间的角度,以及正弦定律(两次):

    double inverseRadius = 1.0 / radius;
    double lineLength = Math.sqrt( dx * dx + dy * dy );
    double cornerdx = cornerX - start.x;
    double cornerdy = cornerY - start.y;
    double cornerdist = Math.sqrt( cornerdx * cornerdx + cornerdy * cornerdy );
    double innerAngle = Math.acos( (cornerdx * dx + cornerdy * dy) / (lineLength * cornerdist) );
    double innerAngleSin = Math.sin( innerAngle );
    double angle1Sin = innerAngleSin * cornerdist * inverseRadius;
    
    // The angle is too large, there cannot be an intersection
    if (Math.abs( angle1Sin ) > 1.0f) {
       return null;
    }
    
    double angle1 = Math.PI - Math.asin( angle1Sin );
    double angle2 = Math.PI - innerAngle - angle1;
    double intersectionDistance = radius * Math.sin( angle2 ) / innerAngleSin;
    

    现在我们解决了所有边和角度,我们可以确定时间和其他所有内容:

    // Solve for time
    float time = (float)(intersectionDistance / lineLength);
    
    // If time is outside the boundaries, return null. This algorithm can 
    // return a negative time which indicates the previous intersection. 
    if (time > 1.0f || time < 0.0f) {
       return null;
    }
    
    // Solve the intersection and normal
    float ix = time * dx + start.x;
    float iy = time * dy + start.y;
    float nx = (float)((ix - cornerX) * inverseRadius);
    float ny = (float)((iy - cornerY) * inverseRadius);
    
    return new Intersection( ix, iy, time, nx, ny );
    

    哇!这很有趣……就效率而言,这有很大的改进空间。您可以对侧交叉点检查重新排序,以便尽早逃脱,同时尽可能少地进行计算。

    我希望有一种方法可以在没有三角函数的情况下做到这一点,但我不得不让步!

    这是我调用它并使用它来计算圆的新位置的示例,使用法线进行反射,并使用相交时间来计算反射幅度:

    Intersection inter = handleIntersection( bounds, start, end, radius );
    
    if (inter != null) 
    {
       // Project Future Position
       float remainingTime = 1.0f - inter.time;
       float dx = end.x - start.x;
       float dy = end.y - start.y;
       float dot = dx * inter.nx + dy * inter.ny;
       float ndx = dx - 2 * dot * inter.nx;
       float ndy = dy - 2 * dot * inter.ny;
       float newx = inter.x + ndx * remainingTime;
       float newy = inter.y + ndy * remainingTime;
       // new circle position = {newx, newy}
     }
    

    我已经在pastebin 上发布了完整的代码,其中包含一个完全交互式的示例,您可以在其中绘制起点和终点,并显示时间和由此产生的矩形反弹。

    如果您想让它立即运行,您必须从 my blog 下载代码,否则将其粘贴到您自己的 Java2D 应用程序中。

    编辑: 我已经修改了 pastebin 中的代码以包含碰撞点,并且还做了一些速度改进。

    编辑: 您可以通过使用该矩形的角度来修改旋转矩形,以取消旋转具有圆形起点和终点的矩形。您将执行相交检查,然后旋转生成的点和法线。

    编辑: 如果圆的路径的边界体积不与矩形相交,我修改了 pastebin 上的代码以提前退出。

    【讨论】:

    【解决方案2】:

    找到接触的时刻并不难:

    您需要在碰撞前的时间步长 (B) 和之后的时间步长 (A) 处的圆和矩形的位置。计算圆心到它在A和B时刻碰撞的矩形的直线的距离(即点到直线距离的常用公式),那么碰撞的时间为:

    tC = dt*(dB-R)/(dA+dB),
    

    其中tC是碰撞时间,dt是时间步长,dB是碰撞前到直线的距离,dA是碰撞后的距离,R是圆的半径。

    这假设一切都是局部线性的,也就是说,您的时间步长相当小,因此速度等在您计算碰撞的时间步长内不会发生太大变化。毕竟,这是时间步长的点:在足够小的时间步长下,非线性问题是局部线性的。在上面的等式中,我利用了这一点:dB-R 是圆到直线的距离,dA+dB 是移动的总距离,所以这个问题只是将距离比等同于时间比,假设一切都是近似线性的时间步长内。 (当然,在碰撞时刻,线性近似并不是最好的,但要找到碰撞时刻,问题是它是否在 到碰撞时刻的时间步长内是线性的。)

    【讨论】:

      【解决方案3】:

      这是一个非线性问题,对吧?

      您采取一个时间步,并通过使用该步开始时的速度计算出的位移来移动球。如果发现重叠,请减小步长并重新计算直到收敛。

      您是否假设球和矩形都是刚性的,没有变形?无摩擦接触?接触后你将如何处理球的运动?你是在转换到接触的坐标系(法线+切线),计算,然后再转换回来吗?

      这不是一个小问题。

      也许您应该研究一个物理引擎,例如Box2D,而不是自己编写代码。

      【讨论】:

      • 如果我理解正确,您的解决方案只是一个近似值,而且速度非常慢......我想用方程而不是循环来找到变量 t。
      • 不,你不明白这是一个非线性解决方案。没有一般的方程解。
      • 如果我找到了 a 和 b 函数的规律,我可以找到变量 t。查看“类似问题”链接中的问题。
      • 不,我对物理学足够了解。
      猜你喜欢
      • 1970-01-01
      • 2013-03-27
      • 2013-09-12
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 2023-01-26
      • 2018-07-14
      • 1970-01-01
      相关资源
      最近更新 更多