Daire-Dikdörtgen çarpışma algılama (kavşak)


192

2D Öklid uzayında bir dairenin ve bir dikdörtgenin kesişip kesişmediğini nasıl anlayabilirim? (yani klasik 2D geometrisi)


1
Dikdörtgen her zaman eksenlerle hizalanmış mı yoksa keyfi bir açıyla döndürülebilir mi?
e.James

11
@ eJames: bunun önemi nedir? Dikdörtgeni daire ile kesişme için kontrol ediyorsunuz ; her zaman koordinat sisteminizi dönüştürebilirsiniz, böylece dikdörtgen daire içinde değişiklik olmadan eksene paralel olur :-)
ShreevatsaR

Bunu bir cevap olarak, -Θ ve tümüyle döndürerek eklemelisiniz ...
aib

2
@ShreevatsaR: Bu koordinat çevirisi konusunda endişelenmem gerekip gerekmediği açısından önemlidir. @aib: Ah canım!
e.James

Yanıtlar:


191

Dairenin dikdörtgenle kesiştiği sadece iki durum vardır:

  • Dairenin merkezi dikdörtgenin içinde ya da
  • Dikdörtgenin kenarlarından birinin daire içinde bir noktası vardır.

Bunun, dikdörtgenin eksen paralel olmasını gerektirmediğini unutmayın.

Daire ve dikdörtgenin kesişebileceği farklı yollar

(Bunu görmenin bir yolu: kenarlardan hiçbirinin dairede bir noktası yoksa (tüm kenarlar dairenin tamamen dışındaysa), o zaman dairenin çokgene kesebilmesinin tek yolu, kenarın tamamen içinde kalmasıdır. çokgen.)

Daire merkezi nerede anlayış ile, aşağıdaki gibi bir şey, çalışacak Pve yarıçapı Rve dikdörtgen kesişim noktası var A, B, C, Dbu sırayla (tam değil 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)))

Herhangi bir geometri yazıyorsanız muhtemelen kütüphanenizde yukarıdaki işlevler zaten vardır. Aksi takdirde, pointInRectangle()çeşitli şekillerde uygulanabilir; çokgen yöntemlerindeki genel noktalardan herhangi biri çalışır, ancak bir dikdörtgen için bunun işe yarayıp yaramadığını kontrol edebilirsiniz:

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

Ve intersectCircle() uygulanması da kolaydır: bir yol P, çizgiden dik olan ayağın yeterince yakın ve uç noktalar arasında olup olmadığını kontrol etmek ve aksi takdirde uç noktaları kontrol etmek olacaktır.

Güzel olan şey, aynı fikrin sadece dikdörtgenler için değil, herhangi bir basit çokgenle bir dairenin kesişimi için de işe yaramasıdır - dışbükey bile olmak zorunda değildir!


25
Değeri için, gerçekten bu cevabın benden daha iyi olduğunu düşünüyorum. İki ana neden: 1: Dikdörtgenin eksene paralel olmaması durumunda döndürme gerektirmez ve 2: konsept tüm çokgenlere kolayca uzanır .
James James

2
@paniq: Her ikisi de sabit zaman. :-) Ama evet, bu genel bir çözüm olarak, herhangi bir yönelim ile dikdörtgenleri ve aslında herhangi bir basit çokgeni kapsayan daha yararlıdır.
ShreevatsaR

7
Dikdörtgenin tamamen dairenin içinde olduğu, ancak dairenin merkezinin dikdörtgenin içinde olmadığı durumdan ne haber?
ericsoco

2
@ericsoco: İyi gözlem. :-) Sanırım "dikdörtgenin kenarlarından birinde" diski kesişiyor "demeliydim, çünkü çemberin sınırını değil, çemberin kendisiyle bir nokta paylaştığı anlamına geliyordu. Her neyse, yukarıdaki açıklama, "P [dairenin merkezi] 'den çizgiye dik olan ayağın yeterince yakın ve uç noktalar arasında olup olmadığını kontrol edin ve aksi takdirde uç noktaları kontrol edin" aksi takdirde uç noktalar dairenin içinde uzanır (ör. disk).
ShreevatsaR

2
@ DexD.Hunter Dairenin merkezi dikdörtgenin dışındaysa, ancak bir kısmı dikdörtgenin içindeyse, dikdörtgenin kenarlarından biri daireyle kesişir.
ShreevatsaR

289

İşte nasıl yaparım:

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

Şöyle çalışır:

illusration

  1. İlk çizgi çifti dairenin merkezi ile dikdörtgenin merkezi arasındaki x ve y farklarının mutlak değerlerini hesaplar. Bu, dört çeyreği bire indirir, böylece hesaplamaların dört kez yapılması gerekmez. Görüntü, dairenin merkezinin şimdi yatması gereken alanı göstermektedir. Yalnızca tek çeyrek daire gösterildiğine dikkat edin. Dikdörtgen gri alandır ve kırmızı kenarlık, dikdörtgenin kenarlarından tam olarak bir yarıçap olan kritik alanı ana hatlarıyla belirtir. Kesişimin meydana gelmesi için dairenin merkezi bu kırmızı kenarlığın içinde olmalıdır.

  2. İkinci çizgi çifti, dairenin kesişmenin mümkün olmadığı dikdörtgenden (her iki yönde) yeterince uzakta olduğu kolay durumları ortadan kaldırır. Bu, görüntüdeki yeşil alana karşılık gelir.

  3. Üçüncü satır çifti, dairenin kesişimin garanti edildiği dikdörtgene (her iki yönde) yeterince yakın olduğu kolay durumları ele alır. Bu, görüntüdeki turuncu ve gri bölümlere karşılık gelir. Bu adımın mantığın anlamlı olması için 2. adımdan sonra yapılması gerektiğini unutmayın.

  4. Kalan çizgiler dairenin dikdörtgenin köşesiyle kesişebileceği zor durumu hesaplar. Çözmek için, dairenin merkezinden ve köşeden uzaklığı hesaplayın ve sonra mesafenin dairenin yarıçapından fazla olmadığını doğrulayın. Bu hesaplama, merkezi kırmızı gölgeli alan içindeki tüm daireler için false değerini ve merkezi beyaz gölgeli alan içindeki tüm daireler için true değerini döndürür.


4
Çok hoş! Notlar: Görünüşe göre burada, rect.x / y dikdörtgenin sağ üst köşesindedir. Ayrıca, bunun yerine yarıçapın karesiyle karşılaştırarak pahalı kare kökü de ortadan kaldırabilirsiniz.
luqui

2
Oh hayır, benim hatam. rect.x / y, dikdörtgenin sol alt tarafındadır. Ben şunu yazardım: circleDistance.x = abs (circle.x - (rect.x + rect.width / 2));
luqui

2
@ Tanner: İşte başlıyoruz. Yedekler ve OKB için Yaşasın;)
e.James

11
sadece açıklığa kavuşturmak için - bu cevap yalnızca eksene hizalanmış dikdörtgenler için geçerlidir. bu, diğer cevaplar hakkındaki yorumları okumaktan açıktır, ancak bu cevaptan + açık yorumlardan belli değildir. (ekseni hizalı rects için harika bir cevap tho!)
ericsoco

3
Harika! Okuyucular için burada bir rektin tanımının rect.x & rect.y'nin rektifin merkezi olduğuna inandığını bilmek önemlidir . Benim dünyamda bir circleDistance_x = abs(circle.x - (rect.x-rect.w/2)); circleDistance_y = abs(circle.y - (rect.y-rect.h/2));
rect'in xy'si rect'in

123

İşte uygulanması oldukça basit (ve oldukça hızlı) başka bir çözüm. Kürenin dikdörtgeni tam olarak girmesi de dahil olmak üzere tüm kavşakları yakalayacaktır.

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

Herhangi bir iyi matematik kütüphanesi ile, bu 3 veya 4 satıra kısaltılabilir.


3
Orada bir hata var, üst ve alt değil, sol ve sağ ile en yakınY için arama, aksi takdirde güzel bir çözüm.
manveru

8
Bu cevabı en çok beğendim. Kısa, anlaşılması kolay ve hızlı.
John Kurlak

2
Dikdörtgen x ve y eksenlerine eğikse çözümünüzün başarısız olduğunu düşünüyorum.
Aslan

3
@Leo Bence bu algoritmayı bu duruma uyacak şekilde değiştirmek zor değil, sadece orijin dikdörtgen merkezinde ve dikdörtgen artık eğik olmayan bir koordinat dönüşümü uygulanmalıdır. Dönüşümü yalnızca daire merkezine uygulamanız gerekir.
enobayram

1
Bu temelde Objective-C'ye taşıdığım migapro.com/circle-and-rotated-rectangle-collision-detection adresindeki kodla aynıdır . Çok iyi çalışıyor; soruna güzel bir çözüm.
PKCLsoft

10

küreniz ve doğrultunuz IIF
ile daire merkezi ve rektinizin bir tepe noktası arasındaki mesafe kürenizin yarıçapından daha küçüktür
VEYA
daire merkezi ile rektinizin bir kenarı arasındaki mesafe kürenizin yarıçapından daha küçüktür ( [ nokta çizgisi mesafesi ])
VEYA
daire merkezi rektetin içindedir

noktası mesafesinin :

P1 = [x1, y1]
P2 = [x2, y2]
Mesafe = sqrt (abs (x1 - x2) + abs (y1-y2))

nokta çizgisi mesafesi:

L1 = [x1, y1], L2 = [x2, y2] (çizginizin iki noktası, yani tepe noktaları)
P1 = [px, py] bir nokta

Mesafe d = abs ((x2-x1) (y1-py) - (x1-px) (y2-y1)) / Mesafe (L1, L2)


rect içindeki daire merkezi:
ayırıcı bir eksen yaklaşımı alın: dikdörtgeni noktadan ayıran bir çizgi üzerinde bir projeksiyon varsa, kesişmezler

noktayı rektifinizin kenarlarına paralel çizgiler üzerine yansıtırsınız ve kesişip kesişmediklerini kolayca belirleyebilirsiniz. 4 projeksiyonun hepsinde kesişmezlerse, onlar (nokta ve dikdörtgen) kesişemez.

sadece iç ürüne ihtiyacınız var (x = [x1, x2], y = [y1, y2], x * y = x1 * y1 + x2 * y2)

testiniz şöyle görünecektir:

// dikdörtgen kenarlar: TL (sol üst), TR (sağ üst), BL (sol alt), BR (sağ alt)
// test edilecek nokta: İÇN

ayrılmış = yanlış
{{TL, TR}, {BL, BR}, {TL, BL}, {TR-BR}} 'deki egde için: // kenarlar
    D = kenar [0] - kenar [1]
    innerProd = D * POI
    Aralık_dak = dak (D * kenar [0], D * kenar [1])
    Aralık_maks = maksimum (D * kenar [0], D * kenar [1])
    değilse (Aralık_dakika ≤ içProd ≤ Aralık_maks) 
           ayrılmış = doğru
           break // end for loop 
    eğer biterse
sonu
eğer (ayrılmış doğrudur)    
      dönüş "kavşak yok"
Başka 
      dönüş "kavşak"
eğer biterse

bu, eksene hizalanmış bir dikdörtgeni kabul etmez ve dışbükey kümeler arasındaki kesişimleri test etmek için kolayca genişletilebilir.


1
Noktadan noktaya mesafe abs değil kare kullanmalı mıydı?
Thomas

6

Bu en hızlı çözüm:

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

Yürütme sırasına dikkat edin ve genişlik / yüksekliğin yarısı önceden hesaplanır. Ayrıca, bazı saat çevrimlerini kaydetmek için kareleme "manuel olarak" yapılır.


3
En pahalı kod yolundaki beş testin / karşılaştırmanın kanıt olmadan “en hızlı çözüm” olduğunu iddia edebileceğinizi sanmıyorum.
sam hocevar


1
Bu yöntemle ilgili tecrübelerime göre, çarpışma çoğu zaman gerçekleşmez. Bu nedenle, testlerin çoğu kod yürütülmeden önce bir çıkışa neden olur.
intrepidis

6

Ortaya koyduğum en basit çözüm oldukça basit.

Dikdörtgendeki daireye en yakın noktayı bularak mesafeyi karşılaştırarak çalışır.

Tüm bunları birkaç işlemle yapabilir ve hatta sqrt işlevinden kaçınabilirsiniz.

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

Ve bu kadar! Yukarıdaki çözüm, x ekseni aşağı bakacak şekilde dünyanın sol üst köşesinde bir başlangıç ​​noktası olduğunu varsaymaktadır.

Hareketli bir daire ile dikdörtgen arasındaki çarpışmaları ele almak için bir çözüm istiyorsanız, çok daha karmaşıktır ve benim başka bir cevabımla kaplıdır .


Daire yarıçapı çok küçükse ve merkezi dikdörtgenin içindeyse kavşakları algılayamaz!
Yoav

2
Bunu başarısız kılan gerçek girdi sağlayabilir misiniz? Daire içerideyken, testin sol kısmı 0.0'dır. Yarıçap sıfır olmadığı sürece testin sağ kısmı> 0.0
ClickerMonkey

Bu döndürülmüş dikdörtgenler için de işe yarar mı? değilse o zaman bana bu konuda bir ipucu verin .....
M Abdul Sami

4

Aslında bu çok daha basit. Sadece iki şeye ihtiyacınız var.

İlk olarak, daire merkezinden dikdörtgenin her satırına dört dikey mesafe bulmanız gerekir . Üçü daire yarıçapından daha büyükse, daireniz dikdörtgenle kesişmez.

İkincisi, daire merkezi ve dikdörtgen merkezi arasındaki mesafeyi bulmanız gerekir, o zaman mesafe dikdörtgen diyagonal uzunluğun yarısından daha büyükse daire dikdörtgenin içinde olmaz.

İyi şanslar!


3

İşte bir küre ve eksen olmayan hizalanmış bir kutu arasındaki bir çarpışmayı çözmek için C kodum. Kendi kütüphane rutinlerime dayanıyor, ancak bazıları için yararlı olabilir. Bir oyunda kullanıyorum ve mükemmel çalışıyor.

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

Görselleştirmek için klavyenizin sayısal tuş takımını kullanın. Eğer '5' tuşu dikdörtgeni temsil ediyorsa, 1-9 arasındaki tüm tuşlar 9 çeyrek alanı temsil eder ve dikdörtgeni oluşturan çizgilere bölünür (5 içeride olacak şekilde).

1) Dairenin merkezi 4. çeyrekte ise (yani dikdörtgenin içinde), iki şekil kesişir.

Bu yoldan çekildiğinde, iki olası durum vardır: a) Daire, dikdörtgenin iki veya daha fazla komşu kenarı ile kesişir. b) Daire, dikdörtgenin bir kenarı ile kesişir.

İlk durum basit. Daire, dikdörtgenin iki komşu kenarı ile kesişirse, bu iki kenarı birleştiren köşeyi içermelidir. (Bu ya da merkezi, daha önce kapladığımız kadran 5'te yatıyor. Ayrıca, dairenin dikdörtgenin sadece iki karşıt kenarı ile kesiştiği durumun da kapsanacağına dikkat edin .)

2) Dikdörtgenin A, B, C, D köşelerinden herhangi biri dairenin içinde ise, iki şekil kesişir.

İkinci durum daha hileli. Bunun sadece dairenin merkezi 2, 4, 6 veya 8 çeyreklerden birinde olduğunda meydana gelebileceğini not etmeliyiz (Aslında, merkez 1, 3, 7, 8 çeyreklerden herhangi birinde ise, karşılık gelen köşe ona en yakın nokta olacaktır.)

Şimdi dairenin merkezinin 'kenar' kadranlarından birinde olduğu ve sadece karşılık gelen kenarla kesiştiği durumumuz var. Ardından, kenarda dairenin merkezine en yakın nokta dairenin içinde olmalıdır.

3) Her AB, BC, CD, DA hattı için dairenin merkezinden P dikey çizgiler p (AB, P), p (BC, P), p (CD, P), p (DA, P) oluşturun. her dikey çizgi, eğer orijinal kenarla kesişme dairenin içinde yer alıyorsa, iki şekil kesişir.

Bu son adım için bir kısayol var. Dairenin merkezi kadranda 8 ise ve AB kenarı üst kenarsa, kesişme noktasında A ve B'nin y koordinatı ve P merkezinin x koordinatı olacaktır.

Dört çizgi kavşağını yapılandırabilir ve karşılık gelen kenarlarında olup olmadıklarını kontrol edebilir veya hangi çeyrek daire P'nin olduğunu ve ilgili kavşağı kontrol edebilirsiniz. Her ikisi de aynı boole denklemini basitleştirmelidir. Yukarıdaki 2. adımın P'nin 'köşe' çeyrek çemberlerinden birinde bulunmadığına dikkat etmediğinizden emin olun; sadece bir kavşak aradı.

Düzenleme: Görünüşe göre, # 2 yukarıdaki # 3 bir alt harf olduğu basit gerçeği göz ardı ettik. Sonuçta, köşeler de kenarlardaki noktalardır. Harika bir açıklama için @ ShreevatsaR'ın cevabına bakınız. Ve bu arada, hızlı ama gereksiz bir kontrol istemiyorsanız yukarıdaki # 2'yi unutun.


2

Bu işlev Daire ve Dikdörtgen arasındaki çarpışmaları (kesişimleri) algılar. Cevabında e.James yöntemi gibi çalışıyor, ancak bu, dikdörtgenin tüm açıları için çarpışmaları tespit ediyor (sadece sağ köşede değil).

NOT:

aRect.origin.x ve aRect.origin.y , dikdörtgenin sol alt açısının koordinatlarıdır!

aCircle.x ve aCircle.y , Circle Center'ın koordinatlarıdır!

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

Gerekli değilse pahalı pisagorlardan kaçınan bir yöntemim var. Dikdörtgenin ve dairenin sınırlayıcı kutuları kesişmediğinde.

Ve öklidyen olmayanlar için de işe yarayacak:

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, minLon, maxY ve minYon ile değiştirilebilir, minLon: minX, maxX ile değiştirin
  • normDist ist tam mesafe hesaplama sonra biraz daha hızlı bir yöntem. Öklid uzayında kare kökü olmadan Örn (veya Haversine için diğer şeyler bir sürü olmadan): dLat=(lat-circleY); dLon=(lon-circleX); normed=dLat*dLat+dLon*dLon. Tabii ki bu normDist yöntemini kullanırsanız normedDist = dist*dist;, çevre için bir

Tam Bkz BBOX ve Çember benim kodunu GraphHopper projesi.


1

Şekillerle çalışmak için sınıf oluşturdum umarım beğenirsiniz

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

İşte% 100 çalışan değiştirilmiş kod:

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

İşte bunun için hızlı bir satırlık test:

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

Bu, rect_halvesdikdörtgenin ortasından köşeye doğru pozitif bir vektörün bulunduğu eksene hizalanmış durumdur . İçindeki ifade , dikdörtgenin en yakın noktasına length()kadar bir delta vektörüdür center. Bu her boyutta çalışır.


1
  • Öncelikle, dikdörtgenin ve daireye teğet olan karenin çakışıp çakışmadığını kontrol edin (kolay). Eğer örtüşmezlerse çarpışmazlar.
  • Dairenin merkezinin dikdörtgenin içinde olup olmadığını kontrol edin (kolay). Eğer içerideyse, çarpışırlar.
  • Dikdörtgen kenarlardan dairenin merkezine olan minimum kare mesafesini hesaplayın (biraz sert). Kare yarıçapından daha düşükse, çarpışırlar, aksi takdirde çarpmazlar.

Verimlidir, çünkü:

  • İlk olarak en yaygın senaryoyu ucuz bir algoritma ile kontrol eder ve çarpışmadığından emin olduğunda sona erer.
  • Daha sonra bir sonraki en yaygın senaryoyu ucuz bir algoritma ile kontrol eder (karekök hesaplamayın, kare değerleri kullanın) ve çarpıştığından emin olduklarında sona erer.
  • Daha sonra dikdörtgen kenarlıklarla çarpışmayı kontrol etmek için daha pahalı algoritmayı çalıştırır.

1

benim için çalıştı (sadece dikdörtgen açısı 180 olduğunda çalışır)

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 ... Buna oy verdim ama sonra düzgün bir şekilde test ettim ve örneğin köşelerde işe yaramadığını düşünüyorum. İki dikdörtgen için işe yarar.
Dan Zen

1

E.James'in cevabını biraz geliştirmek :

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

Bu toplayıp çıkarmadan rect.w / 2ve rect.h / 2bir kez yerine üç kereye kadar.


Çoğu modern derleyicinin gereksiz hesaplamaları sizin için otomatik olarak optimize edeceğinden (veya en azından) şüpheleniyorum.
martineau

0

SQL ile Coğrafi Koordinatlarda Çember / Dikdörtgen çarpışmasını hesaplamak zorunda olanlar için,
bu benim e.James önerilen algoritmasının kehanet 11'inde benim uygulama .

Girişte daire koordinatları, km cinsinden daire yarıçapı ve dikdörtgenin iki köşe koordinatı gerekir:

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

İşler, bunu bir hafta önce anladım ve şimdi test ettim.

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 için işe yarayabilir, ancak soru Circle-Rectangle hakkındadır.
martineau

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

Dikdörtgenin dört kenarına sahip olduğunuzu varsayarsak, kenarlardan dairenin merkezine olan mesafeyi kontrol edin, eğer yarıçaptan daha azsa, şekiller kesişmektedir.

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

Küçük bir dairenin tamamen büyük bir dikdörtgenle çevrelenmesine ne dersiniz? Elbette bu bir kavşaktır ve bu cevapta testi geçemez.
Ken Paul

Ah evet, bunu düşünmedim. Sadece sqrt ((rectangleRight.x / 2 - circleCenter.x) ^ 2 + (rectangleBottom.y / 2 - circleCenter.y) ^ 2) <yarıçapı kesişirse daha uzun kontroller ekleyebilirsiniz. Bu uzun ve yavaş olacaktır, ama başımın üstünden gelebileceğim en iyisi bu.
ForYourOwnGood

Kenarların herhangi birindeki [tek bir] noktada kesişebilirler. Kenar merkezi mesafelerini de bulmalısınız. (Oh, ve köşelerini "köşeleri" olarak adlandır :)
aib

Bu, yalnızca dairenin içinde bir köşe olduğunda algılanır.
stark

-2

Dikdörtgen daireyle kesişiyorsa, dikdörtgenin bir veya daha fazla köşe noktasının dairenin içinde olması gerekir. Bir dikdörtgenin dört noktasının A, B, C, D olduğunu varsayalım. bunlardan en az biri daireyle kesişmelidir. böylece bir noktadan dairenin merkezine olan mesafe dairenin yarıçapından daha azsa daireyle kesişmelidir. Mesafeyi elde etmek için Pisagor teoremini kullanabilirsiniz,

H^2 = A^2 + B^2

Bu tekniğin bazı sınırları vardır. Ancak oyun geliştiricileri için daha iyi çalışacaktır. özellikle çarpışma tespiti

Arvo'nun Algoritması için iyi bir güncelleme


Dikdörtgenin dairenin yarıçapından daha büyük bir kenarı olduğunda bu cevap inanılmaz derecede yanlıştır.
Paul K
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.