java - 如何修复碰撞响应中的圆形和矩形重叠?

标签 java collision-detection physics collision game-physics

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

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

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

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

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

enter image description here

地点:

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

  • r是圆的半径。

  • (x, y)是圆心

  • (v.x, v.y)是它的速度。

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

最近我解决了一个similar problem对于圆之间的碰撞,但现在我不知道函数A和B的规律。

最佳答案

经过多年盯着这个问题,但从未想出一个完美的解决方案,我终于做到了!

这几乎是一种直接的算法,不需要循环和近似。

这是它在更高层次上的工作方式:

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

现在是血淋淋的细节!

函数的输入是边界(具有左、上、右、下)和当前点(起点)和 future 点(终点)。

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

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

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

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;

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

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);
}

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

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 上发布了完整的代码带有一个完全交互式的示例,您可以在其中绘制起点和终点,它会向您显示时间和矩形反弹的结果。

Example

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

编辑: 我修改了 pastebin 中的代码以也包括碰撞点,并且还进行了一些速度改进。

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

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

关于java - 如何修复碰撞响应中的圆形和矩形重叠?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18704999/

相关文章:

java - 尝试从字符串中解析Int会抛出 "java.lang.NullPointerException"

java - 如何在安装程序之外更新 install4j 中的响应文件

algorithm - 检测体素或体素组是否仍连接到对象的其余部分

python - (Python + pygame) 用矩形进行碰撞检测

java - LWJGL 碰撞 3D OpenGL

algorithm - 在CIEXYZ到CIELUV的转换中, `u'`, `v'`, `u' ₙ`, ` v' ₙ`, and ` Yₙ`是什么?

wolfram-mathematica - mathematica 中带下标的物理常数

java - Android DialogFragment 自动调整大小

java - 使用 Scanner 读取 Float 时出现 Eclipse 错误

python - python 3.2.2中的碰撞检测和 mask