本文翻译自《Circle and Rotated Rectangle Collision Detection》,敬请勘误。
圆与旋转矩形的碰撞检测
本文我将解释如何实现圆和旋转矩形的碰撞检测。碰撞检测用于确定对象A是否碰撞了对象B。圆包含圆心位置x,y和一个半径。矩形包含左上角的x,y位置、宽度、长度和一个可以旋转的角度。我们假设矩形沿着它的中心点旋转。
我将使用一个小程序、图片和代码来展示这一点。为了更好地理解圆和不旋转矩形之间的碰撞检测,可以阅读这篇文章
程序示例
这里有一个程序示例来展示碰撞检测。如果检测到碰撞,图形就变为蓝色。绿色轮廓形状表示该用于计算的状态。橙色的线将未旋转圆的圆心与矩形上最近的一个点连接。你可以设置一个角度,点击按钮使其旋转。点击或者拖动某个地方使圆形移动。
设置一个角度实现圆形和矩形碰撞检测
想法很简单。为了计算方便,我们需要在矩形角度为0时,记下两个形状的位置。如果我们将矩形旋转一个角度,计算将会有一点复杂。将矩形保持0度角时候通过简单的加法就可以很容易地获得其他点的位置坐标。左上角为(x,y),右上角为(x+width, y),左下角为(x, y+height)。
这里我们不旋转矩形,而是用同样的角度旋转圆。将圆心放在矩形角度为0时的位置。单点比较容易旋转。如下简图所示,蓝色区域代表我们所看到的矩形在旋转。黑色区域用于计算。我们用0度调整形状,换言之,我们将矩形设为0度,用矩形的角度来旋转圆。
所用的公式如下所示。变量cx/cy代表圆心点,originX/originY代表旋转圆的点,x’/y’为旋转点。
x’= cos(theta) * (cx – originX) – sin(theta) * (cy – originY) + originX
y’= sin(theta) * (cx – originX) + cos(theta) * (cy – originY) + originY
找到离未旋转的圆和矩形最近的点,我们需要做的就是分别为x和y做if-else判断。
对于x,使用矩形的left x(rx)和right x(rx+width)与圆心x(cx)做比较。如果cx在rx的左边,那么rx离x最近。如果cx在rx+width的右边,那么rx+width离x最近。最后如果以上两点都不符合,那么cx本身离x最近。如下图所示,红色线表示x的位置。
对于y,与x的规则相同。如果圆心y(cy)在top y(ry)的上面,那么ry离y点最近。如果cy在bottom y(ry+height)的下面,ry+heigh离y最近。如果以上两点都不符合,那么cy离y最近。
最后,我们需要计算未旋转圆的圆心和我们所找到的最近点的距离。我使用勾股定理计算这个最近距离(a^2 + b^2 = c^2)。然后将这个值与圆的半径作比较,如果这个距离远远小于半径,那么这两个图形就会发生碰撞。
代码实例
1
2
3
4
5
6
7
8
9
10
11
|
// Rotate circle's center point backdouble unrotatedCircleX = Math.cos(rect.angle) * (circle.x - rect.centerX) -
Math.
sin
(rect.angle) * (circle.y - rect.centerY) + rect.centerX;
double
unrotatedCircleY = Math.
sin
(rect.angle) * (circle.x - rect.centerX) +
Math.
cos
(rect.angle) * (circle.y - rect.centerY) + rect.centerY;
// Closest point in the rectangle to the center of circle rotated backwards(unrotated)double closestX, closestY; // Find the unrotated closest x point from center of unrotated circleif (unrotatedCircleX < rect.x)
closestX = rect.x;
else
if
(unrotatedCircleX > rect.x + rect.width)
closestX = rect.x + rect.width;
else
closestX = unrotatedCircleX ;
// Find the unrotated closest y point from center of unrotated circleif (unrotatedCircleY < rect.y)
closestY = rect.y;
else
if
(unrotatedCircleY > rect.y + rect.height)
closestY = rect.y + rect.height;
else
closestY = unrotatedCircleY;
// Determine collisionboolean collision = false; double distance = findDistance(unrotatedCircleX , unrotatedCircleY, closestX, closestY);if (distance < circle.radius)
collision =
true
;
// Collisionelse
collision =
false
;
|
1
2
3
4
5
6
7
8
9
|
/**
* Pythagorean theorem
* @param fromX
* @param fromY
* @param toX
* @param toY
*/
public
double
findDistance(
double
fromX,
double
fromY,
double
toX,
double
toY){
double
a = Math.
abs
(fromX - toX);
double
b = Math.
abs
(fromY - toY);
return
Math.
sqrt
((a * a) + (b * b));}
|