如何判断在二维欧几里得空间中一个圆和一个矩形是否相交?(即经典的二维几何学)
如何判断在二维欧几里得空间中一个圆和一个矩形是否相交?(即经典的二维几何学)
这是我会如何做的:
bool intersects(CircleType circle, RectType rect)
{
circleDistance.x = abs(circle.x - rect.x);
circleDistance.y = abs(circle.y - rect.y);
if (circleDistance.x > (rect.width/2 + circle.r)) { return false; }
if (circleDistance.y > (rect.height/2 + circle.r)) { return false; }
if (circleDistance.x <= (rect.width/2)) { return true; }
if (circleDistance.y <= (rect.height/2)) { return true; }
cornerDistance_sq = (circleDistance.x - rect.width/2)^2 +
(circleDistance.y - rect.height/2)^2;
return (cornerDistance_sq <= (circle.r^2));
}
下面是它的工作原理:
第一对代码计算圆心与矩形中心的x和y差的绝对值。这将四个象限缩成一个,从而省去了重复计算。图像显示了圆心现在必须位于其中的区域。请注意,仅显示单个象限。矩形是灰色区域,红色边框勾画了临界区域,该区域距离矩形边缘正好有一个半径。圆的中心必须在此红色边界内才能发生交集。
第二对代码消除了圆远离矩形的简单情况(两个方向)。这对应于图像中的绿色区域。
第三对代码处理了圆足够靠近矩形的简单情况(两个方向),这样就保证了交集的存在。这对应于图像中的橙色和灰色部分。请注意,这一步骤必须在步骤2之后执行,以使逻辑合理。
其余代码计算了圆可能与矩形角相交的困难情况。为解决此问题,计算圆心与角之间的距离,然后验证该距离是否不超过圆的半径。对于所有中心位于红色阴影区域内的圆,此计算返回false,并对于所有中心位于白色阴影区域内的圆,此计算返回true。
circleDistance_x = abs(circle.x - (rect.x-rect.w/2)); circleDistance_y = abs(circle.y - (rect.y-rect.h/2));
- erco圆形和矩形相交只有两种情况:
注意,这不需要矩形是轴对齐的。
(一种方法是:如果没有边缘点在圆内(如果所有边缘完全“在外部”),那么圆仍然与多边形相交的唯一方法是它完全位于多边形内部。)
有了这个洞见,像以下代码就可以实现,其中圆形的中心为P
,半径为R
,矩形的顶点按顺序为A
、B
、C
、D
(不是完整的代码):
def intersect(Circle(P, R), Rectangle(A, B, C, D)):
S = Circle(P, R)
return (pointInRectangle(P, Rectangle(A, B, C, D)) or
intersectCircle(S, (A, B)) or
intersectCircle(S, (B, C)) or
intersectCircle(S, (C, D)) or
intersectCircle(S, (D, A)))
如果您正在编写任何几何代码,您可能已经在库中拥有上述函数。否则,pointInRectangle()
可以采用多种方式实现; 任何通用的点在多边形内部方法都可以使用,但对于矩形,您只需检查这是否有效:
0 ≤ AP·AB ≤ AB·AB and 0 ≤ AP·AD ≤ AD·AD
而且intersectCircle()
也很容易实现:一种方法是检查从P
到线的垂足是否足够接近并在端点之间,并在否则检查端点。
很酷的事情是,相同的想法不仅适用于矩形,还适用于圆与任何简单多边形的交集——甚至不必是凸多边形!
pointInRectangle
和intersectCircle
也不是显而易见的;这就是为什么我解释了每个函数可能的一个实现方式,即使每个函数可能已经在其他问题中得到了解答。(顺便说一下,这些东西对我来说仍然不是显而易见的;这就是添加证明的原因。这个答案是2008年写的;我只是在2017年添加了图片。)我只是分享我的理解,并没有打算让你感到不悦。 :-) - ShreevatsaR这里有另一种方案,实现起来相当简单(而且速度也相当快)。 它将捕获所有交集,包括球体完全进入矩形的情况。
// clamp(value, min, max) - limits value to the range min..max
// Find the closest point to the circle within the rectangle
float closestX = clamp(circle.X, rectangle.Left, rectangle.Right);
float closestY = clamp(circle.Y, rectangle.Top, rectangle.Bottom);
// Calculate the distance between the circle's center and this closest point
float distanceX = circle.X - closestX;
float distanceY = circle.Y - closestY;
// If the distance is less than the circle's radius, an intersection occurs
float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
return distanceSquared < (circle.Radius * circle.Radius);
使用任何一个好的数学库,这可以缩短为3或4行。
我想到的最简单解决方法非常直接。它的原理是找到离圆形最近的矩形上的点,然后比较它们之间的距离。你可以用几个操作完成所有这些步骤,甚至可以避免使用sqrt函数。
public boolean intersects(float cx, float cy, float radius, float left, float top, float right, float bottom)
{
float closestX = (cx < left ? left : (cx > right ? right : cx));
float closestY = (cy < top ? top : (cy > bottom ? bottom : cy));
float dx = closestX - cx;
float dy = closestY - cy;
return ( dx * dx + dy * dy ) <= radius * radius;
}
就是这样了!上面的解决方案假设世界的原点在左上角,x轴向下。
如果你想要解决移动圆和矩形之间碰撞的问题,那就更加复杂,可以参考我在另一个答案中提供的解决方案。
如果你的圆和矩形相交,则需要满足以下条件之一:
- 圆心到矩形一个顶点的距离小于圆的半径
- 圆心到矩形一条边的距离小于圆的半径(参考点线距离)
- 圆心在矩形内部
点与点之间的距离:
P1 = [x1,y1] P2 = [x2,y2] 距离 = sqrt(abs(x1 - x2)+abs(y1-y2))
点到线的距离:
L1 = [x1,y1],L2 = [x2,y2](线的两个端点) P1 = [px,py](某个点)
距离 d = abs( (x2-x1)(y1-py)-(x1-px)(y2-y1) ) / Distance(L1,L2)
圆心在矩形内部:
采用分隔轴方法:如果存在可以将矩形与点分开的投影到一条直线上,则它们不相交。
对于矩形的每条边,你需要将点投影到平行于该边的线上,然后判断它们是否相交。如果它们不在所有4个投影上都相交,则它们(点和矩形)不相交。
你只需要使用点积( x= [x1,x2] , y = [y1,y2] , x*y = x1*y1 + x2*y2 )
相关代码如下:
//矩形边缘:TL(左上角)、TR(右上角)、BL(左下角)、BR(右下角) //要测试的点:POI
seperated = false for egde in { {TL,TR}, {BL,BR}, {TL,BL},{TR-BR} }: // 矩形边缘 D = edge[0] - edge[1] 内积 = D * POI
Interval_min = min(D*edge[0],D*edge[1]) Interval_max = max(D*edge[0],D*edge[1]) 如果不满足 ( Interval_min ≤ innerProd ≤ Interval_max ) seperated = true break // 结束 for 循环 end if end for 如果 (seperated 是 true) 返回 "无交集" else 返回 "有交集" end if这并不假定轴对齐的矩形,并且可以轻松扩展以测试凸集之间的相交。
这是最快的解决方案:
public static boolean intersect(Rectangle r, Circle c)
{
float cx = Math.abs(c.x - r.x - r.halfWidth);
float xDist = r.halfWidth + c.radius;
if (cx > xDist)
return false;
float cy = Math.abs(c.y - r.y - r.halfHeight);
float yDist = r.halfHeight + c.radius;
if (cy > yDist)
return false;
if (cx <= r.halfWidth || cy <= r.halfHeight)
return true;
float xCornerDist = cx - r.halfWidth;
float yCornerDist = cy - r.halfHeight;
float xCornerDistSq = xCornerDist * xCornerDist;
float yCornerDistSq = yCornerDist * yCornerDist;
float maxCornerDistSq = c.radius * c.radius;
return xCornerDistSq + yCornerDistSq <= maxCornerDistSq;
}
const hasIntersection = ({x: cx, y: cy, r: cr}, {x, y, width, height}) => {
const distX = Math.abs(cx - x - width / 2);
const distY = Math.abs(cy - y - height / 2);
if (distX > (width / 2 + cr)) {
return false;
}
if (distY > (height / 2 + cr)) {
return false;
}
if (distX <= (width / 2)) {
return true;
}
if (distY <= (height / 2)) {
return true;
}
const Δx = distX - width / 2;
const Δy = distY - height / 2;
return Δx * Δx + Δy * Δy <= cr * cr;
};
const rect = new DOMRect(50, 20, 100, 50);
const circ1 = new DOMPoint(160, 80);
circ1.r = 20;
const circ2 = new DOMPoint(80, 95);
circ2.r = 20;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
ctx.beginPath();
ctx.strokeStyle = hasIntersection(circ1, rect) ? 'red' : 'green';
ctx.arc(circ1.x, circ1.y, circ1.r, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = hasIntersection(circ2, rect) ? 'red' : 'green';
ctx.arc(circ2.x, circ2.y, circ2.r, 0, 2 * Math.PI);
ctx.stroke();
<canvas id="canvas"></canvas>
提示:不必旋转矩形(4个点),而是将圆(1个点)沿相反方向旋转。
float physicsProcessCollisionBetweenSelfAndActorRect(SPhysics *self, SPhysics *actor)
{
float diff = 99999;
SVector relative_position_of_circle = getDifference2DBetweenVectors(&self->worldPosition, &actor->worldPosition);
rotateVector2DBy(&relative_position_of_circle, -actor->axis.angleZ); // This aligns the coord system so the rect becomes an AABB
float x_clamped_within_rectangle = relative_position_of_circle.x;
float y_clamped_within_rectangle = relative_position_of_circle.y;
LIMIT(x_clamped_within_rectangle, actor->physicsRect.l, actor->physicsRect.r);
LIMIT(y_clamped_within_rectangle, actor->physicsRect.b, actor->physicsRect.t);
// Calculate the distance between the circle's center and this closest point
float distance_to_nearest_edge_x = relative_position_of_circle.x - x_clamped_within_rectangle;
float distance_to_nearest_edge_y = relative_position_of_circle.y - y_clamped_within_rectangle;
// If the distance is less than the circle's radius, an intersection occurs
float distance_sq_x = SQUARE(distance_to_nearest_edge_x);
float distance_sq_y = SQUARE(distance_to_nearest_edge_y);
float radius_sq = SQUARE(self->physicsRadius);
if(distance_sq_x + distance_sq_y < radius_sq)
{
float half_rect_w = (actor->physicsRect.r - actor->physicsRect.l) * 0.5f;
float half_rect_h = (actor->physicsRect.t - actor->physicsRect.b) * 0.5f;
CREATE_VECTOR(push_vector);
// If we're at one of the corners of this object, treat this as a circular/circular collision
if(fabs(relative_position_of_circle.x) > half_rect_w && fabs(relative_position_of_circle.y) > half_rect_h)
{
SVector edges;
if(relative_position_of_circle.x > 0) edges.x = half_rect_w; else edges.x = -half_rect_w;
if(relative_position_of_circle.y > 0) edges.y = half_rect_h; else edges.y = -half_rect_h;
push_vector = relative_position_of_circle;
moveVectorByInverseVector2D(&push_vector, &edges);
// We now have the vector from the corner of the rect to the point.
float delta_length = getVector2DMagnitude(&push_vector);
float diff = self->physicsRadius - delta_length; // Find out how far away we are from our ideal distance
// Normalise the vector
push_vector.x /= delta_length;
push_vector.y /= delta_length;
scaleVector2DBy(&push_vector, diff); // Now multiply it by the difference
push_vector.z = 0;
}
else // Nope - just bouncing against one of the edges
{
if(relative_position_of_circle.x > 0) // Ball is to the right
push_vector.x = (half_rect_w + self->physicsRadius) - relative_position_of_circle.x;
else
push_vector.x = -((half_rect_w + self->physicsRadius) + relative_position_of_circle.x);
if(relative_position_of_circle.y > 0) // Ball is above
push_vector.y = (half_rect_h + self->physicsRadius) - relative_position_of_circle.y;
else
push_vector.y = -((half_rect_h + self->physicsRadius) + relative_position_of_circle.y);
if(fabs(push_vector.x) < fabs(push_vector.y))
push_vector.y = 0;
else
push_vector.x = 0;
}
diff = 0; // Cheat, since we don't do anything with the value anyway
rotateVector2DBy(&push_vector, actor->axis.angleZ);
SVector *from = &self->worldPosition;
moveVectorBy2D(from, push_vector.x, push_vector.y);
}
return diff;
}