如何判断圆形和矩形在二维欧几里得空间中是否相交?(即经典的二维几何)
26 回答
这是我的做法:
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。
圆与矩形相交只有两种情况:
- 圆的中心位于矩形内,或者
- 矩形的边缘之一在圆中有一个点。
请注意,这并不要求矩形是轴平行的。
(看到这一点的一种方法:如果没有一条边在圆中有一个点(如果所有边都完全在圆“外部”),那么圆仍然可以与多边形相交的唯一方法是它完全位于多边形。)
有了这种洞察力,类似下面的内容将起作用,其中圆有 centerP
和 radius R
,并且矩形有 vertices 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
到线的垂线的脚是否足够近并且在端点之间,否则检查端点。
很酷的是,同样的想法不仅适用于矩形,也适用于圆与任何简单多边形的交集——甚至不必是凸的!
这是另一种实现起来非常简单(而且速度也非常快)的解决方案。它将捕获所有交叉点,包括球体完全进入矩形时。
// 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 行。
您的球体和矩形相交 IIF
圆心和矩形的一个顶点之间的距离小于球体的半径,
或者
圆心和矩形的一个边缘之间的距离小于球体的半径( [点线距离])
或
圆心在矩形
点点距离内:
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) ) / 距离(L1,L2)
矩形内的圆心:
采用分离轴方法:如果存在将矩形与该点分开的直线上的投影,则它们不相交
您将点投影在与矩形两侧平行的线上,然后可以轻松确定它们是否相交。如果它们不在所有 4 个投影上相交,则它们(点和矩形)不能相交。
你只需要内积( x= [x1,x2] , y = [y1,y2] , x*y = x1*y1 + x2*y2 )
你的测试看起来像这样:
//矩形边:TL(左上)、TR(右上)、BL(左下)、BR(右下) //指向测试:POI 分开=假 for egde in { {TL,TR}, {BL,BR}, {TL,BL},{TR-BR} }: // 边缘 D = 边缘[0] - 边缘[1] innerProd = 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) 分开=真 break // 结束 for 循环 万一 结束 如果(分隔为真) 返回“没有交集” 别的 返回“路口” 万一
这不假定轴对齐的矩形,并且可以轻松扩展以测试凸集之间的交叉点。
我想出的最简单的解决方案非常简单。
它的工作原理是在矩形中找到最接近圆的点,然后比较距离。
您可以通过一些操作完成所有这些操作,甚至可以避免使用 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 轴指向下方。
如果您想要一个处理移动圆和矩形之间碰撞的解决方案,它要复杂得多,并且涵盖在我的另一个答案中。
这是最快的解决方案:
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;
}
注意执行顺序,宽度/高度的一半是预先计算的。此外,平方是“手动”完成的,以节省一些时钟周期。
实际上,这要简单得多。你只需要两件事。
首先,您需要找到从圆心到矩形每条线的四个正交距离。如果其中任何三个大于圆半径,那么您的圆将不会与矩形相交。
其次,您需要找到圆心与矩形中心之间的距离,如果距离大于矩形对角线长度的一半,则您的圆将不在矩形内。
祝你好运!
这是我的 C 代码,用于解决球体和非轴对齐框之间的碰撞。它依赖于我自己的几个库例程,但它可能对某些人有用。我在游戏中使用它并且效果很好。
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;
}
要可视化,请使用键盘的小键盘。如果键 '5' 代表您的矩形,那么所有键 1-9 代表 9 个象限空间除以构成矩形的线(其中 5 是内部。)
1)如果圆的中心在象限5(即矩形内),那么这两个形状相交。
除此之外,有两种可能的情况:a)圆与矩形的两个或多个相邻边缘相交。b) 圆与矩形的一条边相交。
第一种情况很简单。如果圆与矩形的两条相邻边相交,则它必须包含连接这两条边的角。(那个,或者它的中心位于我们已经讨论过的象限 5。还要注意,圆与矩形的两个相对边缘相交的情况也被覆盖了。)
2) 如果矩形的任意角 A、B、C、D 位于圆内,则这两个形状相交。
第二种情况更棘手。我们应该注意,只有当圆的中心位于第 2、4、6 或 8 象限之一时才会发生这种情况。(实际上,如果圆心在 1、3、7、8 中的任何一个象限上,则相应的角将是最接近它的点。)
现在我们有了圆的中心在“边缘”象限之一的情况,它只与相应的边缘相交。然后,边缘上最靠近圆心的点必须位于圆内。
3) 对于每条直线 AB、BC、CD、DA,构造通过圆心 P 的垂线 p(AB,P)、p(BC,P)、p(CD,P)、p(DA,P)。对于每条垂直线,如果与原始边缘的交点位于圆内,则两个形状相交。
最后一步有一个捷径。如果圆心在第 8 象限并且边 AB 是上边,则交点将具有 A 和 B 的 y 坐标,以及中心 P 的 x 坐标。
您可以构造四个线的交点并检查它们是否位于相应的边上,或者找出 P 所在的象限并检查相应的交点。两者都应该简化为相同的布尔方程。请注意,上述步骤 2 并未排除 P 位于“角落”象限之一;它只是在寻找一个十字路口。
编辑:事实证明,我忽略了一个简单的事实,即 #2 是上面 #3 的子案例。毕竟,角落也是边缘上的点。请参阅下面的@ShreevatsaR 的答案以获得很好的解释。同时,忘记上面的#2,除非您想要快速但多余的检查。
此函数检测圆形和矩形之间的碰撞(相交)。他在他的回答中像 e.James 方法一样工作,但是这个方法检测矩形所有角度的碰撞(不仅是右上角)。
笔记:
aRect.origin.x和aRect.origin.y是矩形左下角的坐标!
aCircle.x和aCircle.y是圆心的坐标!
static inline BOOL RectIntersectsCircle(CGRect aRect, Circle aCircle) {
float testX = aCircle.x;
float testY = aCircle.y;
if (testX < aRect.origin.x)
testX = aRect.origin.x;
if (testX > (aRect.origin.x + aRect.size.width))
testX = (aRect.origin.x + aRect.size.width);
if (testY < aRect.origin.y)
testY = aRect.origin.y;
if (testY > (aRect.origin.y + aRect.size.height))
testY = (aRect.origin.y + aRect.size.height);
return ((aCircle.x - testX) * (aCircle.x - testX) + (aCircle.y - testY) * (aCircle.y - testY)) < aCircle.radius * aCircle.radius;
}
稍微改进一下e.James 的答案:
double dx = abs(circle.x - rect.x) - rect.w / 2,
dy = abs(circle.y - rect.y) - rect.h / 2;
if (dx > circle.r || dy > circle.r) { return false; }
if (dx <= 0 || dy <= 0) { return true; }
return (dx * dx + dy * dy <= circle.r * circle.r);
这减去一次rect.w / 2
而rect.h / 2
不是最多三次。
如果您对更图形化的解决方案感兴趣,它甚至可以在(平面内)旋转的矩形上工作..
演示:https ://jsfiddle.net/exodus4d/94mxLvqh/2691/
这个想法是:
- 将场景平移到原点 [0,0]
- 如果矩形不在平面内,则旋转中心应位于 [0, 0]
- 将场景旋转回平面
- 计算交点
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 点)。
这是修改后的代码 100% 工作:
public static bool IsIntersected(PointF circle, float radius, RectangleF rectangle)
{
var rectangleCenter = new PointF((rectangle.X + rectangle.Width / 2),
(rectangle.Y + rectangle.Height / 2));
var w = rectangle.Width / 2;
var h = rectangle.Height / 2;
var dx = Math.Abs(circle.X - rectangleCenter.X);
var dy = Math.Abs(circle.Y - rectangleCenter.Y);
if (dx > (radius + w) || dy > (radius + h)) return false;
var circleDistance = new PointF
{
X = Math.Abs(circle.X - rectangle.X - w),
Y = Math.Abs(circle.Y - rectangle.Y - h)
};
if (circleDistance.X <= (w))
{
return true;
}
if (circleDistance.Y <= (h))
{
return true;
}
var cornerDistanceSq = Math.Pow(circleDistance.X - w, 2) +
Math.Pow(circleDistance.Y - h, 2);
return (cornerDistanceSq <= (Math.Pow(radius, 2)));
}
巴萨姆·阿卢吉里
如果没有必要,我有一种方法可以避免昂贵的毕达哥拉斯 - 即。当矩形和圆形的边界框不相交时。
它也适用于非欧几里得:
class Circle {
// create the bounding box of the circle only once
BBox bbox;
public boolean intersect(BBox b) {
// test top intersect
if (lat > b.maxLat) {
if (lon < b.minLon)
return normDist(b.maxLat, b.minLon) <= normedDist;
if (lon > b.maxLon)
return normDist(b.maxLat, b.maxLon) <= normedDist;
return b.maxLat - bbox.minLat > 0;
}
// test bottom intersect
if (lat < b.minLat) {
if (lon < b.minLon)
return normDist(b.minLat, b.minLon) <= normedDist;
if (lon > b.maxLon)
return normDist(b.minLat, b.maxLon) <= normedDist;
return bbox.maxLat - b.minLat > 0;
}
// test middle intersect
if (lon < b.minLon)
return bbox.maxLon - b.minLon > 0;
if (lon > b.maxLon)
return b.maxLon - bbox.minLon > 0;
return true;
}
}
- minLat,maxLat 可以替换为 minY,maxY 和 minLon, maxLon 相同:替换为 minX, maxX
- normDist 只是比全距离计算快一点的方法。例如,没有欧几里得空间中的平方根(或者没有很多其他的东西用于haversine)
dLat=(lat-circleY); dLon=(lon-circleX); normed=dLat*dLat+dLon*dLon
:。当然,如果您使用该 normDist 方法,则需要normedDist = dist*dist;
为圆创建一个
请参阅我的GraphHopper项目的完整BBox和Circle代码。
我创建了形状工作类希望你喜欢
public class Geomethry {
public static boolean intersectionCircleAndRectangle(int circleX, int circleY, int circleR, int rectangleX, int rectangleY, int rectangleWidth, int rectangleHeight){
boolean result = false;
float rectHalfWidth = rectangleWidth/2.0f;
float rectHalfHeight = rectangleHeight/2.0f;
float rectCenterX = rectangleX + rectHalfWidth;
float rectCenterY = rectangleY + rectHalfHeight;
float deltax = Math.abs(rectCenterX - circleX);
float deltay = Math.abs(rectCenterY - circleY);
float lengthHypotenuseSqure = deltax*deltax + deltay*deltay;
do{
// check that distance between the centerse is more than the distance between the circumcircle of rectangle and circle
if(lengthHypotenuseSqure > ((rectHalfWidth+circleR)*(rectHalfWidth+circleR) + (rectHalfHeight+circleR)*(rectHalfHeight+circleR))){
//System.out.println("distance between the centerse is more than the distance between the circumcircle of rectangle and circle");
break;
}
// check that distance between the centerse is less than the distance between the inscribed circle
float rectMinHalfSide = Math.min(rectHalfWidth, rectHalfHeight);
if(lengthHypotenuseSqure < ((rectMinHalfSide+circleR)*(rectMinHalfSide+circleR))){
//System.out.println("distance between the centerse is less than the distance between the inscribed circle");
result=true;
break;
}
// check that the squares relate to angles
if((deltax > (rectHalfWidth+circleR)*0.9) && (deltay > (rectHalfHeight+circleR)*0.9)){
//System.out.println("squares relate to angles");
result=true;
}
}while(false);
return result;
}
public static boolean intersectionRectangleAndRectangle(int rectangleX, int rectangleY, int rectangleWidth, int rectangleHeight, int rectangleX2, int rectangleY2, int rectangleWidth2, int rectangleHeight2){
boolean result = false;
float rectHalfWidth = rectangleWidth/2.0f;
float rectHalfHeight = rectangleHeight/2.0f;
float rectHalfWidth2 = rectangleWidth2/2.0f;
float rectHalfHeight2 = rectangleHeight2/2.0f;
float deltax = Math.abs((rectangleX + rectHalfWidth) - (rectangleX2 + rectHalfWidth2));
float deltay = Math.abs((rectangleY + rectHalfHeight) - (rectangleY2 + rectHalfHeight2));
float lengthHypotenuseSqure = deltax*deltax + deltay*deltay;
do{
// check that distance between the centerse is more than the distance between the circumcircle
if(lengthHypotenuseSqure > ((rectHalfWidth+rectHalfWidth2)*(rectHalfWidth+rectHalfWidth2) + (rectHalfHeight+rectHalfHeight2)*(rectHalfHeight+rectHalfHeight2))){
//System.out.println("distance between the centerse is more than the distance between the circumcircle");
break;
}
// check that distance between the centerse is less than the distance between the inscribed circle
float rectMinHalfSide = Math.min(rectHalfWidth, rectHalfHeight);
float rectMinHalfSide2 = Math.min(rectHalfWidth2, rectHalfHeight2);
if(lengthHypotenuseSqure < ((rectMinHalfSide+rectMinHalfSide2)*(rectMinHalfSide+rectMinHalfSide2))){
//System.out.println("distance between the centerse is less than the distance between the inscribed circle");
result=true;
break;
}
// check that the squares relate to angles
if((deltax > (rectHalfWidth+rectHalfWidth2)*0.9) && (deltay > (rectHalfHeight+rectHalfHeight2)*0.9)){
//System.out.println("squares relate to angles");
result=true;
}
}while(false);
return result;
}
}
这是一个快速的单线测试:
if (length(max(abs(center - rect_mid) - rect_halves, 0)) <= radius ) {
// They intersect.
}
这是轴对齐的情况,其中rect_halves
是从矩形中间指向角的正向量。里面的表达式是一个从矩形到最近点length()
的增量向量。center
这适用于任何维度。
- 首先检查矩形和与圆相切的正方形是否重叠(简单)。如果它们不重叠,它们就不会碰撞。
- 检查圆的中心是否在矩形内(简单)。如果它在里面,它们就会发生碰撞。
- 计算从矩形边到圆心的最小平方距离(有点困难)。如果它低于平方半径,那么它们会发生碰撞,否则不会。
它很有效,因为:
- 首先,它使用廉价算法检查最常见的场景,当确定它们没有冲突时,它就结束了。
- 然后它使用廉价算法检查下一个最常见的场景(不计算平方根,使用平方值),当确定它们发生冲突时,它就结束了。
- 然后它执行更昂贵的算法来检查与矩形边界的碰撞。
为我工作(仅在矩形角度为 180 时工作)
function intersects(circle, rect) {
let left = rect.x + rect.width > circle.x - circle.radius;
let right = rect.x < circle.x + circle.radius;
let top = rect.y < circle.y + circle.radius;
let bottom = rect.y + rect.height > circle.y - circle.radius;
return left && right && bottom && top;
}
有效,一周前才发现这一点,现在才开始测试它。
double theta = Math.atan2(cir.getX()-sqr.getX()*1.0,
cir.getY()-sqr.getY()*1.0); //radians of the angle
double dBox; //distance from box to edge of box in direction of the circle
if((theta > Math.PI/4 && theta < 3*Math.PI / 4) ||
(theta < -Math.PI/4 && theta > -3*Math.PI / 4)) {
dBox = sqr.getS() / (2*Math.sin(theta));
} else {
dBox = sqr.getS() / (2*Math.cos(theta));
}
boolean touching = (Math.abs(dBox) >=
Math.sqrt(Math.pow(sqr.getX()-cir.getX(), 2) +
Math.pow(sqr.getY()-cir.getY(), 2)));
对于那些必须使用 SQL 计算地理坐标中的圆/矩形碰撞的人,
这是我在e.James 建议算法的 oracle 11 中的实现。
在输入中,它需要圆坐标、以 km 为单位的圆半径和矩形的两个顶点坐标:
CREATE OR REPLACE FUNCTION "DETECT_CIRC_RECT_COLLISION"
(
circleCenterLat IN NUMBER, -- circle Center Latitude
circleCenterLon IN NUMBER, -- circle Center Longitude
circleRadius IN NUMBER, -- circle Radius in KM
rectSWLat IN NUMBER, -- rectangle South West Latitude
rectSWLon IN NUMBER, -- rectangle South West Longitude
rectNELat IN NUMBER, -- rectangle North Est Latitude
rectNELon IN NUMBER -- rectangle North Est Longitude
)
RETURN NUMBER
AS
-- converts km to degrees (use 69 if miles)
kmToDegreeConst NUMBER := 111.045;
-- Remaining rectangle vertices
rectNWLat NUMBER;
rectNWLon NUMBER;
rectSELat NUMBER;
rectSELon NUMBER;
rectHeight NUMBER;
rectWIdth NUMBER;
circleDistanceLat NUMBER;
circleDistanceLon NUMBER;
cornerDistanceSQ NUMBER;
BEGIN
-- Initialization of remaining rectangle vertices
rectNWLat := rectNELat;
rectNWLon := rectSWLon;
rectSELat := rectSWLat;
rectSELon := rectNELon;
-- Rectangle sides length calculation
rectHeight := calc_distance(rectSWLat, rectSWLon, rectNWLat, rectNWLon);
rectWidth := calc_distance(rectSWLat, rectSWLon, rectSELat, rectSELon);
circleDistanceLat := abs( (circleCenterLat * kmToDegreeConst) - ((rectSWLat * kmToDegreeConst) + (rectHeight/2)) );
circleDistanceLon := abs( (circleCenterLon * kmToDegreeConst) - ((rectSWLon * kmToDegreeConst) + (rectWidth/2)) );
IF circleDistanceLon > ((rectWidth/2) + circleRadius) THEN
RETURN -1; -- -1 => NO Collision ; 0 => Collision Detected
END IF;
IF circleDistanceLat > ((rectHeight/2) + circleRadius) THEN
RETURN -1; -- -1 => NO Collision ; 0 => Collision Detected
END IF;
IF circleDistanceLon <= (rectWidth/2) THEN
RETURN 0; -- -1 => NO Collision ; 0 => Collision Detected
END IF;
IF circleDistanceLat <= (rectHeight/2) THEN
RETURN 0; -- -1 => NO Collision ; 0 => Collision Detected
END IF;
cornerDistanceSQ := POWER(circleDistanceLon - (rectWidth/2), 2) + POWER(circleDistanceLat - (rectHeight/2), 2);
IF cornerDistanceSQ <= POWER(circleRadius, 2) THEN
RETURN 0; -- -1 => NO Collision ; 0 => Collision Detected
ELSE
RETURN -1; -- -1 => NO Collision ; 0 => Collision Detected
END IF;
RETURN -1; -- -1 => NO Collision ; 0 => Collision Detected
END;
def colision(rect, circle):
dx = rect.x - circle.x
dy = rect.y - circle.y
distance = (dy**2 + dx**2)**0.5
angle_to = (rect.angle + math.atan2(dx, dy)/3.1415*180.0) % 360
if((angle_to>135 and angle_to<225) or (angle_to>0 and angle_to<45) or (angle_to>315 and angle_to<360)):
if distance <= circle.rad/2.+((rect.height/2.0)*(1.+0.5*abs(math.sin(angle_to*math.pi/180.)))):
return True
else:
if distance <= circle.rad/2.+((rect.width/2.0)*(1.+0.5*abs(math.cos(angle_to*math.pi/180.)))):
return True
return False
我在制作这个游戏时开发了这个算法:https ://mshwf.github.io/mates/
如果圆接触正方形,那么圆的中心线和正方形的中心线之间的距离应该等于(diameter+side)/2
。因此,让我们有一个名为的变量touching
来保存该距离。问题是:我应该考虑哪个中心线:水平还是垂直?考虑这个框架:
每条中心线给出不同的距离,只有一个是没有碰撞的正确指示,但是使用我们的人类直觉是理解自然算法如何工作的开始。
它们没有接触,这意味着两条中心线之间的距离应该大于touching
,这意味着自然算法会选择水平中心线(垂直中心线表示有碰撞!)。通过注意多个圆,您可以知道:如果圆与正方形的垂直延伸相交,则我们选择垂直距离(水平中心线之间),如果圆与水平延伸相交,我们选择水平距离:
再比如4号圆:它与正方形的水平延伸相交,那么我们认为水平距离等于接触。
好的,困难的部分已经揭开,现在我们知道算法将如何工作,但是我们如何知道圆与哪个扩展相交?其实很简单:我们计算最右边x
和最左边之间的距离x
(圆和正方形),对于y轴也是如此,值较大的那个是扩展与相交的轴圆(如果大于diameter+side
则圆在两个正方形扩展之外,如圆 #7)。代码如下所示:
right = Math.max(square.x+square.side, circle.x+circle.rad);
left = Math.min(square.x, circle.x-circle.rad);
bottom = Math.max(square.y+square.side, circle.y+circle.rad);
top = Math.min(square.y, circle.y-circle.rad);
if (right - left > down - top) {
//compare with horizontal distance
}
else {
//compare with vertical distance
}
/*These equations assume that the reference point of the square is at its top left corner, and the reference point of the circle is at its center*/
- 预先检查完全封装矩形的圆是否与圆碰撞。
- 检查圆内的矩形角。
- 对于每条边,看是否有与圆相交的线。将中心点 C 投影到直线 AB 上得到点 D。如果 CD 的长度小于半径,则发生碰撞。
projectionScalar=dot(AC,AB)/(mag(AC)*mag(AB));
if(projectionScalar>=0 && projectionScalar<=1) {
D=A+AB*projectionScalar;
CD=D-C;
if(mag(CD)<circle.radius){
// there was a collision
}
}
有一种非常简单的方法可以做到这一点,您必须在 x 和 y 中夹住一个点,但在正方形内部,而圆的中心位于您需要夹住其中一个垂直轴的两个正方形边界点之间坐标到平行轴,只要确保夹紧的坐标不超过正方形的限制。然后只需获取圆心与夹紧坐标之间的距离,并检查该距离是否小于圆的半径。
我是这样做的(前 4 个点是正方形坐标,其余的是圆点):
bool DoesCircleImpactBox(float x, float y, float x1, float y1, float xc, float yc, float radius){
float ClampedX=0;
float ClampedY=0;
if(xc>=x and xc<=x1){
ClampedX=xc;
}
if(yc>=y and yc<=y1){
ClampedY=yc;
}
radius = radius+1;
if(xc<x) ClampedX=x;
if(xc>x1) ClampedX=x1-1;
if(yc<y) ClampedY=y;
if(yc>y1) ClampedY=y1-1;
float XDif=ClampedX-xc;
XDif=XDif*XDif;
float YDif=ClampedY-yc;
YDif=YDif*YDif;
if(XDif+YDif<=radius*radius) return true;
return false;
}
我的方法:
- 从 OBB / 矩形上/中的圆计算最近点(最近点将位于边缘/角或内部)
- 计算从最近点到圆心的squared_distance(平方距离避免平方根)
- 返回 squared_distance <= 圆半径平方
假设您有矩形的四个边缘,请检查从边缘到圆心的距离,如果它小于半径,则形状相交。
if sqrt((rectangleRight.x - circleCenter.x)^2 +
(rectangleBottom.y - circleCenter.y)^2) < radius
// then they intersect
if sqrt((rectangleRight.x - circleCenter.x)^2 +
(rectangleTop.y - circleCenter.y)^2) < radius
// then they intersect
if sqrt((rectangleLeft.x - circleCenter.x)^2 +
(rectangleTop.y - circleCenter.y)^2) < radius
// then they intersect
if sqrt((rectangleLeft.x - circleCenter.x)^2 +
(rectangleBottom.y - circleCenter.y)^2) < radius
// then they intersect