Phát hiện va chạm hình tròn-hình chữ nhật (giao lộ)


192

Làm thế nào tôi có thể biết liệu một hình tròn và hình chữ nhật có giao nhau trong không gian Euclide 2D không? (tức là hình học 2D cổ điển)


1
Là hình chữ nhật luôn luôn thẳng hàng với các trục, hoặc nó có thể được xoay bởi một góc tùy ý?
e.James

11
@eJames: nó quan trọng như thế nào? Bạn đang kiểm tra hình chữ nhật cho giao điểm với một vòng tròn ; bạn luôn có thể chuyển đổi hệ tọa độ của mình sao cho hình chữ nhật song song với trục không có thay đổi trong vòng tròn :-)
ShreevatsaR

Bạn nên thêm câu đó dưới dạng câu trả lời, xoay vòng qua -Θ và tất cả ...
aib

2
@ShreevatsaR: Nó có quan trọng về mặt thời tiết hay không tôi có cần phải lo lắng về việc phối hợp dịch hay không. @aib: Trời ơi!
e.James

Câu trả lời:


191

Chỉ có hai trường hợp khi vòng tròn giao nhau với hình chữ nhật:

  • Trung tâm của vòng tròn nằm bên trong hình chữ nhật, hoặc
  • Một trong các cạnh của hình chữ nhật có một điểm trong vòng tròn.

Lưu ý rằng điều này không yêu cầu hình chữ nhật phải song song trục.

Một số cách khác nhau một hình tròn và hình chữ nhật có thể giao nhau

(Một cách để thấy điều này: nếu không có cạnh nào có một điểm trong vòng tròn (nếu tất cả các cạnh hoàn toàn "nằm ngoài" vòng tròn), thì cách duy nhất mà vòng tròn vẫn có thể giao nhau với đa giác là nếu nó nằm hoàn toàn bên trong đa giác.)

Với cái nhìn sâu sắc rằng, một cái gì đó như sau sẽ làm việc, nơi mà các vòng tròn có tâm Pvà bán kính R, và hình chữ nhật có các đỉnh A, B, C, Dtheo thứ tự đó (không hoàn thành code):

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

Nếu bạn đang viết bất kỳ hình học nào, có lẽ bạn đã có các chức năng trên trong thư viện của mình. Mặt khác, pointInRectangle()có thể được thực hiện theo nhiều cách; bất kỳ điểm chung nào trong các phương thức đa giác đều hoạt động, nhưng đối với hình chữ nhật, bạn chỉ cần kiểm tra xem điều này có hoạt động không:

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

intersectCircle()cũng dễ thực hiện: một cách sẽ là kiểm tra xem chân vuông góc Pvới đường thẳng có đủ gần và giữa các điểm cuối hay không, và kiểm tra các điểm cuối khác.

Điều thú vị là cùng một ý tưởng hoạt động không chỉ cho hình chữ nhật mà còn cho giao điểm của một vòng tròn với bất kỳ đa giác đơn giản nào - thậm chí không cần phải lồi!


25
Đối với những gì nó có giá trị, tôi thực sự nghĩ rằng câu trả lời này là tốt hơn của tôi. Hai lý do chính: 1: nó không yêu cầu xoay nếu hình chữ nhật không song song trục và, 2: khái niệm dễ dàng mở rộng cho tất cả các đa giác.
e.James

2
@paniq: Vâng, cả hai đều là thời gian không đổi. :-) Nhưng vâng, điều này hữu ích hơn như là một giải pháp chung, bao trùm các hình chữ nhật với bất kỳ hướng nào, và trên thực tế là bất kỳ đa giác đơn giản nào.
ShreevatsaR

7
Thế còn trường hợp hình chữ nhật nằm hoàn toàn bên trong vòng tròn, nhưng tâm của hình tròn không nằm trong hình chữ nhật thì sao?
ericsoco

2
@ericsoco: Quan sát tốt. :-) Tôi đoán tôi nên nói "giao cắt đĩa" trong "một trong các cạnh của hình chữ nhật cắt đường tròn", bởi vì tôi có nghĩa là nó chia sẻ một điểm với chính vòng tròn, không nhất thiết là ranh giới của vòng tròn. Dù sao, mô tả ở trên, "kiểm tra xem chân vuông góc từ P [tâm của vòng tròn] với đường thẳng có đủ gần và giữa các điểm cuối không, và kiểm tra các điểm cuối nếu không" vẫn hoạt động - ví dụ: các điểm cuối nằm bên trong vòng tròn ( đĩa).
ShreevatsaR

2
@ DexD.Hunter Nếu tâm của hình tròn nằm ngoài hình chữ nhật, nhưng một phần của nó nằm bên trong hình chữ nhật, thì nhất thiết phải là một trong các cạnh của hình chữ nhật cắt đường tròn.
ShreevatsaR

289

Đây là cách tôi sẽ làm điều đó:

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

Đây là cách nó hoạt động:

ảo tưởng

  1. Cặp đường đầu tiên tính giá trị tuyệt đối của chênh lệch x và y giữa tâm của đường tròn và tâm của hình chữ nhật. Điều này thu gọn bốn góc phần tư thành một, để các phép tính không phải thực hiện bốn lần. Hình ảnh cho thấy khu vực mà trung tâm của vòng tròn phải nằm. Lưu ý rằng chỉ có góc phần tư duy nhất được hiển thị. Hình chữ nhật là khu vực màu xám và đường viền màu đỏ phác thảo khu vực quan trọng cách chính xác một bán kính so với các cạnh của hình chữ nhật. Tâm của vòng tròn phải nằm trong đường viền màu đỏ này để xảy ra giao lộ.

  2. Cặp đường thứ hai loại bỏ các trường hợp dễ dàng trong đó hình tròn đủ xa hình chữ nhật (theo một trong hai hướng) mà không có giao điểm nào có thể. Điều này tương ứng với khu vực màu xanh lá cây trong hình ảnh.

  3. Cặp đường thứ ba xử lý các trường hợp dễ dàng trong đó vòng tròn đủ gần với hình chữ nhật (theo một trong hai hướng) mà một giao lộ được đảm bảo. Điều này tương ứng với các phần màu cam và màu xám trong hình ảnh. Lưu ý rằng bước này phải được thực hiện sau bước 2 để logic có ý nghĩa.

  4. Các dòng còn lại tính toán trường hợp khó trong đó hình tròn có thể cắt nhau ở góc của hình chữ nhật. Để giải quyết, hãy tính khoảng cách từ tâm của vòng tròn và góc, sau đó xác minh rằng khoảng cách không lớn hơn bán kính của vòng tròn. Tính toán này trả về sai cho tất cả các vòng tròn có tâm nằm trong vùng bóng mờ màu đỏ và trả về true cho tất cả các vòng tròn có tâm nằm trong vùng bóng trắng.


4
Rất đẹp! Ghi chú: rõ ràng ở đây, orth.x / y nằm ở góc trên bên phải của hình chữ nhật. Ngoài ra, bạn có thể loại bỏ căn bậc hai đắt tiền, bằng cách thay vào đó so với bình phương bán kính.
luqui

2
Ồ không, xấu của tôi. orth.x / y nằm ở phía dưới bên trái của hình chữ nhật. Tôi đã có thể viết: circleDistance.x = abs (circle.x - (orth.x + orth. Thong / 2));
luqui

2
@Tanner: Có chúng tôi đi. Hoan hô các bản sao lưu và OCD;)
e.James

11
chỉ để làm rõ - câu trả lời này chỉ áp dụng cho các hình chữ nhật được căn chỉnh theo trục. Điều đó rõ ràng từ việc đọc qua các bình luận về các câu trả lời khác nhưng không rõ ràng từ câu trả lời này + chỉ các bình luận. (câu trả lời tuyệt vời cho các kiến ​​trúc căn chỉnh trục tho!)
ericsoco

3
Tuyệt quá! Điều quan trọng là người đọc cần biết rằng ở đây tôi tin rằng định nghĩa của trực tràng là orth.x & orth.y là trung tâm của trực tràng . Trong thế giới của tôi, một xy của trực tràng là trên cùng / bên trái của trực tràng và 0,0 là trên cùng / bên trái của màn hình, vì vậy tôi đã sử dụng:circleDistance_x = abs(circle.x - (rect.x-rect.w/2)); circleDistance_y = abs(circle.y - (rect.y-rect.h/2));
erco

123

Đây là một giải pháp khác khá đơn giản để thực hiện (và cũng khá nhanh). Nó sẽ bắt tất cả các giao điểm, bao gồm cả khi hình cầu đã hoàn toàn đi vào hình chữ nhật.

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

Với bất kỳ thư viện toán học tử tế nào, có thể rút ngắn xuống còn 3 hoặc 4 dòng.


3
Bạn có một lỗi trong đó, bạn tìm kiếm gần nhất với Trái và Phải, không phải Trên và Dưới, nếu không thì là giải pháp đáng yêu.
manveru

8
Tôi thích câu trả lời này tốt nhất. Nó ngắn gọn, dễ hiểu và nhanh chóng.
John Kurlak

2
Tôi nghĩ rằng giải pháp của bạn thất bại nếu hình chữ nhật xiên vào trục x và y.
Leo

3
@Leo Tôi nghĩ rằng không khó để sửa đổi thuật toán này để phù hợp với trường hợp đó, người ta chỉ cần áp dụng một phép biến đổi tọa độ trong đó gốc tọa độ ở tâm hình chữ nhật và hình chữ nhật không còn xiên nữa. Bạn chỉ cần áp dụng chuyển đổi cho trung tâm vòng tròn.
enobayram

1
Điều này về cơ bản giống như mã được tìm thấy tại Migapro.com/circle-and-rotated-ronymous-collision-detection mà tôi cũng đã chuyển sang Objective-C. Hoạt động rất tốt; đó là một giải pháp tốt cho vấn đề
PKCLsoft

10

hình cầu và trực tràng của bạn giao nhau IIF
khoảng cách giữa tâm vòng tròn và một đỉnh của trực tràng của bạn nhỏ hơn bán kính của hình cầu
HOẶC
khoảng cách giữa tâm vòng tròn và một cạnh của trực tràng của bạn nhỏ hơn bán kính của hình cầu của bạn ( [ khoảng cách điểm dòng ])
HOẶC
tâm vòng tròn nằm trong

khoảng cách điểm trực tràng:

P1 = [x1, y1]
P2 = [x2, y2]
Khoảng cách = sqrt (abs (x1 - x2) + abs (y1-y2))

khoảng cách điểm:

L1 = [x1, y1], L2 = [x2, y2] (hai điểm trên đường thẳng của bạn, tức là các điểm đỉnh)
P1 = [px, py] một số điểm

Khoảng cách d = abs ((x2-x1) (y1-py) - (x1-px) (y2-y1)) / Khoảng cách (L1, L2)


tâm vòng tròn bên trong trực tràng:
lấy một trục aproach tách biệt: nếu có một hình chiếu lên một đường phân cách hình chữ nhật từ điểm đó, chúng không giao nhau

bạn chiếu điểm trên các đường thẳng song song với các cạnh của trực tràng và sau đó có thể dễ dàng xác định xem chúng có giao nhau không. nếu chúng không giao nhau trên cả 4 hình chiếu thì chúng (điểm và hình chữ nhật) không thể giao nhau.

bạn chỉ cần sản phẩm bên trong (x = [x1, x2], y = [y1, y2], x * y = x1 * y1 + x2 * y2)

bài kiểm tra của bạn sẽ như thế:

// các cạnh hình chữ nhật: TL (trên cùng bên trái), TR (trên cùng bên phải), BL (dưới cùng bên trái), BR (dưới cùng bên phải)
// điểm để kiểm tra: POI

tách biệt = sai
for egde in {{TL, TR}, {BL, BR}, {TL, BL}, {TR-BR}}: // các cạnh
    D = cạnh [0] - cạnh [1]
    InternalProd = D * POI
    Interval_min = min (D * edge [0], D * edge [1])
    Interval_max = max (D * edge [0], D * edge [1])
    nếu không (Interval_min InternalProd ≤ Interval_max) 
           tách biệt = đúng
           ngắt // kết thúc vòng lặp 
    kết thúc nếu
kết thúc cho
nếu (tách biệt là đúng)    
      trả lại "không có giao lộ"
khác 
      trả lại "ngã tư"
kết thúc nếu

điều này không giả sử một hình chữ nhật được căn chỉnh theo trục và có thể dễ dàng mở rộng để kiểm tra các giao điểm giữa các bộ lồi.


1
Không nên sử dụng khoảng cách điểm đến điểm bằng cách sử dụng hình vuông chứ không phải abs?
Thomas

6

Đây là giải pháp nhanh nhất:

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

Lưu ý thứ tự thực hiện và một nửa chiều rộng / chiều cao được tính toán trước. Ngoài ra bình phương được thực hiện "thủ công" để lưu một số chu kỳ đồng hồ.


3
Tôi không nghĩ rằng bạn có thể tuyên bố rằng năm thử nghiệm / so sánh trong đường dẫn mã đắt nhất là giải pháp nhanh nhất mà không cần bằng chứng.
sam hocevar


1
Theo kinh nghiệm của tôi với phương pháp này, va chạm không xảy ra hầu hết thời gian. Do đó, các bài kiểm tra sẽ gây ra một lối thoát trước khi phần lớn mã được thực thi.
intrepidis

6

Giải pháp đơn giản nhất mà tôi nghĩ ra là khá đơn giản.

Nó hoạt động bằng cách tìm điểm trong hình chữ nhật gần nhất với vòng tròn, sau đó so sánh khoảng cách.

Bạn có thể thực hiện tất cả điều này với một vài thao tác và thậm chí tránh chức năng 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;
}

Và đó là nó! Giải pháp trên giả định nguồn gốc ở phía trên bên trái của thế giới với trục x chỉ xuống.

Nếu bạn muốn một giải pháp để xử lý các va chạm giữa một vòng tròn chuyển động và hình chữ nhật, thì nó phức tạp hơn nhiều và nằm trong một câu trả lời khác của tôi.


Điều này sẽ không phát hiện ra các giao điểm nếu bán kính vòng tròn quá nhỏ và tâm của nó nằm trong hình chữ nhật!
Yoav

2
Bạn có thể cung cấp đầu vào thực tế làm cho điều này thất bại? Khi vòng tròn ở bên trong, phần bên trái của bài kiểm tra là 0,0. Trừ khi bán kính bằng 0, phần bên phải của bài kiểm tra phải> 0,0
ClickerMonkey

Điều này sẽ làm việc cho hình chữ nhật xoay quá? nếu không thì xin vui lòng cho tôi một gợi ý về điều đó .....
M Abdul Sami

4

Trên thực tế, điều này đơn giản hơn nhiều. Bạn chỉ cần hai điều.

Đầu tiên, bạn cần tìm bốn khoảng cách trực giao từ tâm vòng tròn đến từng đường của hình chữ nhật. Sau đó, vòng tròn của bạn sẽ không giao nhau với hình chữ nhật nếu bất kỳ ba trong số chúng lớn hơn bán kính vòng tròn.

Thứ hai, bạn cần tìm khoảng cách giữa tâm hình tròn và tâm hình chữ nhật, sau đó bạn khoanh tròn sẽ không nằm trong hình chữ nhật nếu khoảng cách lớn hơn một nửa chiều dài đường chéo hình chữ nhật.

Chúc may mắn!


3

Đây là mã C của tôi để giải quyết xung đột giữa hình cầu và hộp không khớp trục. Nó dựa vào một vài thói quen thư viện của riêng tôi, nhưng nó có thể hữu ích với một số người. Tôi đang sử dụng nó trong một trò chơi và nó hoạt động hoàn hảo.

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

2

Để hình dung, hãy lấy bàn phím của bàn phím của bạn. Nếu khóa '5' đại diện cho hình chữ nhật của bạn, thì tất cả các phím 1-9 đại diện cho 9 góc phần tư chia cho các đường tạo nên hình chữ nhật của bạn (với 5 là bên trong.)

1) Nếu tâm của hình tròn nằm trong góc phần tư 5 (tức là bên trong hình chữ nhật) thì hai hình dạng giao nhau.

Ngoài ra, có hai trường hợp có thể xảy ra: a) Vòng tròn giao nhau với hai hoặc nhiều cạnh lân cận của hình chữ nhật. b) Đường tròn cắt với một cạnh của hình chữ nhật.

Trường hợp đầu tiên là đơn giản. Nếu hình tròn giao nhau với hai cạnh lân cận của hình chữ nhật, nó phải chứa góc nối hai cạnh đó. (Điều đó, hoặc trung tâm của nó nằm ở góc phần tư 5, mà chúng ta đã che. Cũng lưu ý rằng trường hợp đường tròn giao nhau chỉ có hai cạnh đối diện của hình chữ nhật cũng được che phủ.)

2) Nếu bất kỳ góc A, B, C, D nào của hình chữ nhật nằm bên trong đường tròn thì hai hình giao nhau.

Trường hợp thứ hai là khó khăn hơn. Chúng ta nên lưu ý rằng điều đó chỉ có thể xảy ra khi tâm của vòng tròn nằm ở một trong các góc phần tư 2, 4, 6 hoặc 8. (Trên thực tế, nếu tâm nằm trên bất kỳ góc phần tư 1, 3, 7, 8, góc tương ứng sẽ là điểm gần nhất với nó.)

Bây giờ chúng ta có trường hợp rằng tâm của vòng tròn nằm trong một trong các góc phần tư 'cạnh' và nó chỉ giao nhau với cạnh tương ứng. Sau đó, điểm trên cạnh gần nhất với tâm của vòng tròn, phải nằm trong vòng tròn.

3) Với mỗi đường thẳng AB, BC, CD, DA, xây dựng các đường vuông góc p (AB, P), p (BC, P), p (CD, P), p (DA, P) qua tâm của đường tròn P. For mỗi đường vuông góc, nếu giao điểm với cạnh ban đầu nằm bên trong đường tròn thì hai hình sẽ giao nhau.

Có một lối tắt cho bước cuối cùng này. Nếu tâm của đường tròn nằm trong góc phần tư 8 và cạnh AB là cạnh trên, điểm giao nhau sẽ có tọa độ y của A và B và tọa độ x của tâm P.

Bạn có thể xây dựng bốn giao điểm đường và kiểm tra xem chúng có nằm trên các cạnh tương ứng của chúng không, hoặc tìm ra góc phần tư P nằm trong và kiểm tra giao điểm tương ứng. Cả hai nên đơn giản hóa để cùng một phương trình boolean. Hãy cảnh giác rằng bước 2 ở trên không loại trừ P nằm trong một trong các góc phần tư 'góc'; nó chỉ tìm một ngã tư.

Chỉnh sửa: Hóa ra, tôi đã bỏ qua một thực tế đơn giản rằng # 2 là một chữ con của số 3 ở trên. Rốt cuộc, các góc cũng là điểm trên các cạnh. Xem câu trả lời của @ ShreevatsaR dưới đây để được giải thích tuyệt vời. Và trong khi đó, hãy quên # 2 ở trên trừ khi bạn muốn kiểm tra nhanh nhưng không cần thiết.


2

Hàm này phát hiện các va chạm (giao điểm) giữa Vòng tròn và Hình chữ nhật. Anh ta làm việc như phương pháp e.James trong câu trả lời của mình, nhưng điều này phát hiện ra sự va chạm cho tất cả các góc của hình chữ nhật (không chỉ góc phải lên).

GHI CHÚ:

aRect.origin.xaRect.origin.y là tọa độ của góc dưới bên trái của hình chữ nhật!

aCircle.xaCircle.y là tọa độ của Circle Center!

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

1

Tôi có một phương pháp tránh các pythagoras đắt tiền nếu không cần thiết - tức là. khi các hộp giới hạn của hình chữ nhật và hình tròn không giao nhau.

Và nó cũng sẽ hoạt động cho cả phi hạt nhân:

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 có thể được thay thế bằng minY, maxY và tương tự cho minLon, maxLon: thay thế nó bằng minX, maxX
  • NormDist ist chỉ là một phương pháp nhanh hơn một chút sau đó tính toán khoảng cách đầy đủ. Ví dụ: không có căn bậc hai trong không gian euclide (hoặc không có nhiều thứ khác cho haversine) : dLat=(lat-circleY); dLon=(lon-circleX); normed=dLat*dLat+dLon*dLon. Tất nhiên, nếu bạn sử dụng phương thức NormDist đó, bạn sẽ cần tạo một normedDist = dist*dist;vòng tròn

Xem mã BBoxCircle đầy đủ của dự án GraphHopper của tôi .


1

Tôi đã tạo lớp cho công việc với hình dạng hy vọng bạn thích

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

1

Đây là mã modfied hoạt động 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)));
}

Bassam Alugili


1

Đây là một thử nghiệm nhanh một dòng cho việc này:

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

Đây là trường hợp căn chỉnh trục trong đó rect_halveslà một vectơ dương chỉ từ hình chữ nhật ở giữa đến một góc. Biểu thức bên trong length()là một vectơ delta từ centerđến một điểm gần nhất trong hình chữ nhật. Điều này hoạt động trong bất kỳ chiều.


1
  • Đầu tiên kiểm tra xem hình chữ nhật và hình vuông tiếp tuyến với hình tròn có trùng nhau không (dễ). Nếu chúng không trùng nhau, chúng không va chạm.
  • Kiểm tra xem tâm của hình tròn có nằm trong hình chữ nhật không (dễ). Nếu nó ở bên trong, chúng va chạm.
  • Tính khoảng cách bình phương tối thiểu từ các cạnh hình chữ nhật đến tâm của hình tròn (hơi khó). Nếu nó thấp hơn bán kính bình phương, thì chúng va chạm, nếu không thì không.

Nó hiệu quả, bởi vì:

  • Đầu tiên, nó kiểm tra kịch bản phổ biến nhất với thuật toán giá rẻ và khi chắc chắn rằng chúng không va chạm, nó sẽ kết thúc.
  • Sau đó, nó kiểm tra kịch bản phổ biến nhất tiếp theo bằng thuật toán giá rẻ (không tính căn bậc hai, sử dụng các giá trị bình phương) và khi chắc chắn chúng va chạm thì kết thúc.
  • Sau đó, nó thực hiện thuật toán đắt tiền hơn để kiểm tra xung đột với các đường viền hình chữ nhật.

1

làm việc cho tôi (chỉ hoạt động khi góc của hình chữ nhật là 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 ... tôi đã bỏ phiếu này nhưng sau đó đã kiểm tra đúng cách và tôi nghĩ rằng nó không hoạt động trên các góc chẳng hạn. Nó sẽ làm việc cho hai hình chữ nhật.
Dan Zen

1

Cải thiện một chút câu trả lời của 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);

Điều này trừ rect.w / 2rect.h / 2một lần thay vì lên đến ba lần.


0

Đối với những người phải tính toán xung đột Vòng tròn / Hình chữ nhật trong Tọa độ địa lý với SQL,
đây là triển khai của tôi trong thuật toán đề xuất 11 của e.James .

Trong đầu vào, nó yêu cầu tọa độ hình tròn, bán kính hình tròn tính bằng km và hai tọa độ đỉnh của hình chữ nhật:

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;    

0

Hoạt động, chỉ cần tìm ra điều này một tuần trước, và bây giờ phải thử nghiệm nó.

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

Có thể làm việc cho Circle-Square, nhưng câu hỏi là về Circle-hình chữ nhật.
martineau 7/12/2015

0
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

-2

Giả sử bạn có bốn cạnh của hình chữ nhật kiểm tra khoảng cách từ các cạnh đến tâm của vòng tròn, nếu nó nhỏ hơn bán kính, thì các hình dạng được giao nhau.

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

Còn trường hợp một vòng tròn nhỏ được bao bọc hoàn toàn bởi một hình chữ nhật lớn thì sao? Chắc chắn đó là một giao lộ, và sẽ thất bại trong bài kiểm tra trong câu trả lời này.
Ken Paul

À đúng rồi, tôi không nghĩ về điều đó. Bạn chỉ có thể thêm nhiều kiểm tra như nếu sqrt ((hình chữ nhậtRight.x / 2 - circleCenter.x) ^ 2 + (hình chữ nhậtBottom.y / 2 - circleCenter.y) ^ 2) <radius thì chúng giao nhau Điều này sẽ dài và chậm, nhưng trên đỉnh đầu tôi là thứ tốt nhất tôi có thể nghĩ ra.
ForYourOwnood

Chúng có thể giao nhau trên bất kỳ điểm [một nào] trên bất kỳ cạnh nào. Bạn nên tìm khoảng cách trung tâm cạnh là tốt. (Ồ, và gọi các góc của bạn là "góc" :)
aib

Điều này dường như chỉ phát hiện khi một góc nằm trong vòng tròn.
stark

-2

Nếu hình chữ nhật cắt với hình tròn, một hoặc nhiều điểm góc của hình chữ nhật sẽ nằm trong hình tròn. Giả sử bốn điểm của hình chữ nhật là A, B, C, D. ít nhất một trong số chúng nên giao nhau trong vòng tròn. do đó, nếu khoảng cách từ một điểm đến tâm của vòng tròn nhỏ hơn bán kính của vòng tròn thì nó sẽ cắt đường tròn. Để có được khoảng cách, bạn có thể sử dụng định lý Pythagore,

H^2 = A^2 + B^2

Kỹ thuật này có một số giới hạn. Nhưng nó sẽ làm việc tốt hơn cho các nhà phát triển trò chơi. đặc biệt là phát hiện va chạm

Đây là một bản cập nhật tốt cho Thuật toán của Arvo


Câu trả lời này là vô cùng sai bất cứ khi nào hình chữ nhật có cạnh lớn hơn bán kính của hình tròn.
Paul K
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.