【问题标题】:Circle-Rectangle collision detection (intersection)圆-矩形碰撞检测(相交)
【发布时间】:2010-09-28 22:43:48
【问题描述】:

如何判断圆形和矩形在二维欧几里得空间中是否相交? (即经典的二维几何)

【问题讨论】:

  • 矩形是否始终与轴对齐,还是可以任意角度旋转?
  • @eJames:这有什么关系?您正在检查矩形是否与 circle 相交;你总是可以变换你的坐标系,使矩形是轴平行的,圆没有变化:-)
  • 您应该添加它作为答案,旋转 -Θ 和所有...
  • @ShreevatsaR:重要的是我是否需要担心坐标转换。 @aib:天哪!

标签: geometry collision-detection


【解决方案1】:

我的方法:

  1. 从 OBB / 矩形上/内的圆计算最近点 (最近点将位于边缘/角落或内部)
  2. 计算从最近点到圆心的平方距离 (平方距离避免平方根)
  3. 返回 squared_distance

【讨论】:

    【解决方案2】:

    如果您对更图形化的解决方案感兴趣,甚至可以在(平面内)旋转矩形上工作..

    演示:https://jsfiddle.net/exodus4d/94mxLvqh/2691/

    这个想法是:

    1. 翻译场景到原点 [0,0]
      • 如果矩形不在平面内,旋转中心应该在 [0, 0]
    2. 将场景旋转回到平面
    3. 计算交点

    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();
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;

    提示:不要旋转矩形(4 点)。您可以向相反方向旋转圆(1 点)。

    【讨论】:

      【解决方案3】:

      有一种非常简单的方法可以做到这一点,您必须在 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;
      }
      

      【讨论】:

        【解决方案4】:
        1. 预先检查完全包围矩形的圆是否与圆发生碰撞。
        2. 检查圆内的矩形角。
        3. 对于每条边,查看是否有与圆相交的线。将中心点 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
                }
            }
        

        【讨论】:

          【解决方案5】:

          我在制作这个游戏时开发了这个算法: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*/
          

          【讨论】:

            【解决方案6】:

            改进一点answer of 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 一次,而不是最多 3 次。

            【讨论】:

            • 我强烈怀疑大多数现代编译器会(或至少可以)自动为您优化冗余计算。
            • martineau - 不,我没有直接在一个计算中加入多个计算。我更改了它们以在此过程中删除这些额外的计算。
            • 我的观点是,现在许多编译器可能会优化生成的机器代码,以便 dx 和 dy 值的计算只发生一次(不需要你明确地这样做)。
            【解决方案7】:

            为我工作(仅在矩形角度为 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;
            }
            

            【讨论】:

            • hmmm... 我投了赞成票,但随后测试正确,我认为它不适用于例如角落。它适用于两个矩形。
            【解决方案8】:
            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
            

            【讨论】:

              【解决方案9】:

              圆与矩形相交只有两种情况:

              • 圆的中心位于矩形内,或者
              • 矩形的一条边在圆中有一个点。

              请注意,这并不要求矩形是轴平行的。

              (看这个的一种方法:如果没有一条边在圆中有一个点(如果所有边都完全在圆“外部”),那么圆仍然可以与多边形相交的唯一方法是它是否位于完全在多边形内。)

              有了这种洞察力,类似下面的东西就可以工作了,圆的中心是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() 可以通过多种方式实现;任何一般的point in polygon 方法都可以,但是对于矩形,您可以检查它是否有效:

              0 ≤ AP·AB ≤ AB·AB and 0 ≤ AP·AD ≤ AD·AD
              

              而且intersectCircle() 也很容易实现:一种方法是检查从P 到直线的垂线的脚是否足够近并且在端点之间,否则检查端点。

              很酷的是,same 的想法不仅适用于矩形,还适用于圆与任何simple polygon 的交点——甚至不必是凸的!

              【讨论】:

              • 对于它的价值,我真的认为这个答案比我的要好。两个主要原因:1:如果矩形不平行轴,则不需要旋转,2:这个概念很容易扩展到所有多边形。
              • 长方形完全在圆内,而圆心不在长方形内怎么办?
              • @ericsoco:很好的观察。 :-) 我想我应该在“矩形的一个边缘与圆相交”中说“与圆盘相交”,因为我的意思是它与圆本身共享一个点,而不一定是圆的边界。无论如何,上面的描述,“检查从 P [圆的中心] 到直线的垂线的脚是否足够接近并且在端点之间,否则检查端点”仍然有效 - 例如端点位于圆(圆盘)内。
              • 我发现这个答案被高估了。当然,它看起来有精美的图表和代码示例。但这只是解释一些明显的东西的烟雾和镜子,然后最终将实现作为练习留给读者。如果我们有神奇的“lineIntersectsCircle”或“pointInRectangle”库函数,我们可能在那个库中也已经有了“rectangleIntersectsCircle”函数!
              • @PaulK 你一定比我聪明。 :-) 这对我来说不是“显而易见的东西”;我必须弄清楚检查这些条件就足够了。同样,如何实现pointInRectangle 和intersectCircle 并不明显;这就是为什么我解释了一种可能的方式来实现它们中的每一个,即使每个都有很多方式(可能在其他问题上得到回答)。 (顺便说一句,所有这些东西对我来说仍然并不明显;这就是添加证明的原因。答案是在 2008 年写的;我只在 2017 年添加了图片。)我只是分享我的理解,并且无意引起你的反感。 :-)
              【解决方案10】:

              有效,一周前才发现这一点,现在才开始测试。

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

              【讨论】:

              • 可能适用于 Circle-Square,但问题是关于 Circle-Rectangle。
              【解决方案11】:
              • 首先检查矩形和与圆相切的正方形是否重叠(简单)。如果它们不重叠,它们就不会发生碰撞。
              • 检查圆心是否在矩形内(简单)。如果它在里面,它们就会发生碰撞。
              • 计算矩形边到圆心的最小平方距离(有点困难)。如果它低于平方半径,则它们会发生碰撞,否则不会发生碰撞。

              它很有效,因为:

              • 首先,它使用廉价算法检查最常见的场景,当确定它们没有发生冲突时,它就结束了。
              • 然后它使用廉价算法检查下一个最常见的场景(不计算平方根,使用平方值),并在确定它们发生冲突时结束。
              • 然后它执行更昂贵的算法来检查与矩形边框的碰撞。

              【讨论】:

                【解决方案12】:

                这是一个快速的单行测试:

                if (length(max(abs(center - rect_mid) - rect_halves, 0)) <= radius ) {
                  // They intersect.
                }
                

                这是轴对齐的情况,其中rect_halves 是从矩形中间指向角的正向量。 length() 中的表达式是从center 到矩形中最近点的增量向量。这适用于任何维度。

                【讨论】:

                  【解决方案13】:

                  对于那些必须使用 SQL 计算地理坐标中的圆/矩形碰撞,
                  这是我在 e.James suggested algorithm 的 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;    
                  

                  【讨论】:

                    【解决方案14】:

                    假设您有矩形的四个边缘,请检查从边缘到圆心的距离,如果小于半径,则形状相交。

                    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
                    

                    【讨论】:

                    • 小圆完全被大矩形包围怎么办?当然这是一个交叉点,并且会在这个答案中失败。
                    • 啊,是的,我没想到。您可以添加更多检查,例如 if sqrt( (rectangleRight.x/2 - circleCenter.x)^2 + (rectangleBottom.y/2 - circleCenter.y)^2)
                    • 它们可以在任何边上的任何[单个]点上相交。您还应该找到边缘中心距离。 (哦,把你的角落称为“角落”:)
                    • 这似乎只检测圆角何时位于圆内。
                    【解决方案15】:

                    我想出的最简单的解决方案非常简单。

                    它的工作原理是在矩形中找到最接近圆的点,然后比较距离。

                    您可以通过几个操作完成所有这些操作,甚至可以避免使用 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 轴指向下方。

                    如果你想要一个处理移动圆和矩形之间碰撞的解决方案,它要复杂得多,并且涵盖了in another answer of mine.

                    【讨论】:

                    • 如果圆的半径太小并且它的中心在矩形内,这将无法检测到交叉点!
                    • 你能提供导致失败的实际输入吗?当圆圈在里面时,检验的左边部分是 0.0。除非半径为零,否则测试的右侧部分应 > 0.0
                    • 这也适用于旋转的矩形吗?如果没有,请给我一个提示.....
                    • 谢谢哥们。它对我来说很完美。
                    • 圆的起源是什么?在左上角还是在中间?
                    【解决方案16】:

                    实际上,这要简单得多。你只需要两件事。

                    首先,您需要找到从圆心到矩形每条线的四个正交距离。如果其中任何三个大于圆半径,则您的圆将不会与矩形相交。

                    其次,你需要找到圆心到矩形中心的距离,如果距离大于矩形对角线长度的一半,你的圆就不会在矩形内。

                    祝你好运!

                    【讨论】:

                      【解决方案17】:

                      这是最快的解决方案:

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

                      注意执行顺序,宽度/高度的一半是预先计算好的。平方也是“手动”完成的,以节省一些时钟周期。

                      【讨论】:

                      【解决方案18】:

                      这是修改后的代码 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)));
                      }
                      

                      巴萨姆·阿鲁吉里

                      【讨论】:

                        【解决方案19】:

                        我创建了用于处理形状的类 希望你喜欢

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

                        【讨论】:

                          【解决方案20】:

                          我会这样做:

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

                          它是这样工作的:

                          1. 第一对线计算圆心和矩形中心之间的 x 和 y 差的绝对值。这会将四个象限合并为一个,因此不必进行四次计算。图像显示了圆心现在必须位于的区域。请注意,仅显示了单象限。矩形是灰色区域,红色边框勾勒出距离矩形边缘正好一个半径的关键区域。圆心必须在这个红色边框内才能发生交叉。

                          2. 第二对线消除了圆距矩形足够远(在任一方向上)以至于不可能相交的简单情况。这对应于图像中的绿色区域。

                          3. 第三对线处理简单的情况,即圆与矩形足够接近(在任一方向上)以保证相交。这对应于图像中的橙色和灰色部分。请注意,此步骤必须在第 2 步之后完成才能使逻辑有意义。

                          4. 其余行计算圆可能与矩形角相交的困难情况。要求解,计算圆心到角的距离,然后验证距离不大于圆的半径。此计算对于中心在红色阴影区域内的所有圆返回 false,对于中心在白色阴影区域内的所有圆返回 true。

                          【讨论】:

                          • 非常好!注意:显然,rect.x/y 位于矩形的右上角。您还可以通过与半径的平方进行比较来消除昂贵的平方根。
                          • 哦,不,我的错。 rect.x/y 位于矩形的左下方。我会写:circleDistance.x = abs(circle.x - (rect.x + rect.width/2));
                          • @Tanner:我们开始了。万岁备份和强迫症;)
                          • 只是为了澄清——这个答案只适用于轴对齐的矩形。通过阅读其他答案的 cmets 可以清楚地看到这一点,但仅从这个答案 + cmets 中并不明显。 (轴对齐矩形的好答案!)
                          • 太棒了!重要的是要让读者知道,在这里我相信 rect 的定义是 rect.x & rect.y 是 rect 的 center。在我的世界中,矩形的 xy 是矩形的顶部/左侧,0,0 是屏幕的顶部/左侧,所以我使用:circleDistance_x = abs(circle.x - (rect.x-rect.w/2)); circleDistance_y = abs(circle.y - (rect.y-rect.h/2));
                          【解决方案21】:

                          如果没有必要,我有一种方法可以避免昂贵的毕达哥拉斯 - 即。当矩形和圆形的边界框不相交时。

                          它也适用于非欧几里得:

                          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 只是比全距离计算快一点的方法。例如。没有欧几里得空间的平方根(或者没有很多其他的东西):dLat=(lat-circleY); dLon=(lon-circleX); normed=dLat*dLat+dLon*dLon。当然,如果您使用该 normDist 方法,您需要为圆圈创建一个normedDist = dist*dist;

                          查看我的GraphHopper 项目的完整BBox 和Circle 代码。

                          【讨论】:

                            【解决方案22】:

                            此函数检测圆形和矩形之间的碰撞(相交)。他在回答中的工作方式与 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;
                            }
                            

                            【讨论】:

                              【解决方案23】:

                              这是另一个实施起来非常简单(而且速度也非常快)的解决方案。它会捕获所有的交点,包括球体完全进入矩形时。

                              // 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 行。

                              【讨论】:

                              • 你有一个错误,你用 Left 和 Right 搜索 mosty,而不是 Top 和 Bottom,否则很可爱的解决方案。
                              • 我最喜欢这个答案。它简短、易于理解且速度快。
                              • 我认为如果矩形倾斜于 x 轴和 y 轴,您的解决方案将失败。
                              • @Leo 我认为修改此算法以适应这种情况并不难,人们应该简单地应用坐标变换,其中原点位于矩形中心并且矩形不再倾斜。您只需要将变换应​​用到圆心即可。
                              • 这与在migapro.com/circle-and-rotated-rectangle-collision-detection 找到的代码基本相同,我也将其移植到了Objective-C。效果很好;这是解决问题的好方法。
                              【解决方案24】:

                              这是我的 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;
                              }
                              

                              【讨论】:

                                【解决方案25】:

                                你的球体和矩形相交 IIF
                                圆心和矩形的一个顶点之间的距离小于球体的半径
                                或
                                圆心和矩形的一个边缘之间的距离小于球体的半径 ([point-line distance ])
                                或
                                圆心在矩形内

                                点对点距离:

                                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 循环 万一 结束 如果(分隔为真) 返回“没有交集” 别的 返回“路口” 万一

                                这并不假定一个轴对齐的矩形,并且可以轻松扩展以测试凸集之间的交集。

                                【讨论】:

                                • 点到点的距离不应该用正方形而不是abs吗?
                                【解决方案26】:

                                要进行可视化,请使用键盘的小键盘。如果键 '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(AB,P), p(BC,P), p(CD,P), p(DA,P) P. 对于每条垂直线,如果与原边的交点在圆内,则两个形状相交。

                                最后一步有一个捷径。如果圆心在第 8 象限,边 AB 为上边,则交点的 y 坐标为 A 和 B,x 坐标为圆心 P。

                                您可以构造四个线的交点并检查它们是否位于相应的边缘上,或者找出 P 所在的象限并检查相应的交点。两者都应该简化为相同的布尔方程。请注意,上述步骤 2 并未排除 P 位于“角落”象限之一;它只是在寻找一个十字路口。

                                编辑:事实证明,我忽略了一个简单的事实,即 #2 是上面 #3 的子案例。毕竟,角落也是边缘上的点。请参阅下面的@ShreevatsaR 的答案以获得很好的解释。同时,忘记上面的#2,除非你想要一个快速但多余的检查。

                                【讨论】:

                                  猜你喜欢
                                  • 1970-01-01
                                  • 2014-09-03
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2023-01-26
                                  • 1970-01-01
                                  • 2014-02-07
                                  • 1970-01-01
                                  • 2014-05-05
                                  相关资源
                                  最近更新 更多