2D sınırlayıcı kutu kesişimini çözmenin en hızlı yolu nedir?


62

Her Box nesnesinin x, y, width, height özelliklerine sahip olduğunu ve merkezlerini merkezlerinde bulunduğunu ve nesnelerin ve sınırlayıcı kutuların dönmediğini varsayalım.


Bu eksen veya nesne hizalı sınırlayıcı kutular mı?
tenpn

3
Bu soruyu sorduğunuzda, gelecekteki diğer kavşak türlerini mutlaka test etmeniz gerekir;). Bu nedenle ben önermek LİSTESİ Nesne / Obje kesiştiği hakkında. Tablo, statik olduğu kadar dinamik durumlarda da tüm popüler nesne türleri (kutular, küreler, üçgenler, bisikletçiler, koniler, ...) arasındaki kesişimleri sağlar.
Dave O.

2
Lütfen sınırlayıcı ipuçlarına sorunuzu tekrar yazın. Benim bakış açımdan kutu bir 3d nesneyi ima ediyor.
Dave O.

Yanıtlar:


55

(C-ish sözde kodu - dil optimizasyonlarını uygun şekilde uyarlayın)

bool DoBoxesIntersect(Box a, Box b) {
  return (abs(a.x - b.x) * 2 < (a.width + b.width)) &&
         (abs(a.y - b.y) * 2 < (a.height + b.height));
}

Türkçe: Her eksende, kutuların merkezlerinin kesişecekleri kadar yakın olup olmadığını kontrol edin. Her iki eksende kesişirlerse, kutular kesişir. Yapmazlarsa, yapmazlar.

Kesişen kenardaki dokunmayı saymak istiyorsanız <'yi <= olarak değiştirebilirsiniz. Belirli bir kenar dokunuşla belirli bir formül istiyorsanız, == - kullanamazsınız - bu kenarlara dokunup dokunmadığını, kenarların dokunmadığını söyler. Mantıksal olarak eşdeğer bir şey yapmak istersiniz return DoBoxesIntersectOrTouch(a, b) && !DoBoxesIntersect(a, b).

Tam genişliğe ve tam yüksekliğe ek olarak (veya bunun yerine) yarı genişlik ve yarı yüksekliği depolayarak küçük ancak önemli bir hız artışı elde edebileceğinizi belirtmekte fayda var. Öte yandan, 2d sınırlayıcı kutu kavşağının performans darboğazı olması nadirdir.


9
Bu açıkça kutuların eksen hizalı olduğunu varsayar.
tenpn

1
Abs, özellikle yavaş olmamalıdır - şartlı, en azından yavaş olmamalı ve bunu abs olmadan yapmanın tek yolu (bildiğim kadarıyla) fazladan şartlandırmalar içermesidir.
ZorbaTHut

4
Evet, eksen hizalı kutuları üstleniyor. Açıklandığı gibi yapıları rotasyon göstermenin hiçbir yolu yoktur, bu yüzden güvenli olduğunu hissettim.
ZorbaTHut

3
Actionscript'te hesaplamaları hızlandırmak için bazı iyi ipuçları: (esas olarak calc tamsayısı): lab.polygonal.de/2007/05/10/bitwise-gems-fast-integer-math Bunu da gönderiyorum, çünkü daha hızlı içeriyor Actionscript'teki şeyleri yavaşlatma eğiliminde olan Math.abs () 'in yerine geçmesi (elbette performans açısından kritik şeylerden bahsetme).
bummzack

2
Orjinalleri merkezde, özleri solda değil. 0 ile 10 arasında çalışan bir kutuda aslında "x = 5", 8 ile 12 arasında çalışan bir kutuda "x = 10" olacaktır. Sonunda abs(5 - 10) * 2 < (10 + 4)=> ile son 10 < 14. Topleft-corner-size ile boyutta çalışması için basit bir ince ayar yapmanız gerekecek.
ZorbaTHut

37

Bu, X ve Y ekseni ile hizalı iki dikdörtgen için çalışır.
Her dikdörtgenin özellikleri vardır:
"sol", sol tarafının x koordinatı,
"üst", üst tarafının y koordinatı,
"sağ", sağ tarafının x koordinatı,
"alt", y koordinatı alt tarafı

function IntersectRect(r1:Rectangle, r2:Rectangle):Boolean {
    return !(r2.left > r1.right
        || r2.right < r1.left
        || r2.top > r1.bottom
        || r2.bottom < r1.top);
}

Bunun, + y ekseninin aşağıya ve + x ekseninin sağa yöneldiği (yani tipik ekran / piksel koordinatları) olduğu bir koordinat sistemi için tasarlandığını unutmayın . Bunu, + y'nin yukarı doğru yönlendirildiği tipik bir kartezyen sistemine uyarlamak için, dikey eksenler boyunca yapılan karşılaştırmalar tersine çevrilir, örneğin:

return !(r2.left > r1.right
    || r2.right < r1.left
    || r2.top < r1.bottom
    || r2.bottom > r1.top);

Fikir dikdörtgenler olacaktır bunun üzerine olası tüm koşulları çekebilmektir değil örtüştüğü, sonra da görmek cevabını olumsuzlamak edilir üst üste. Ne olursa olsun eksenlerinin yön, iki dikdörtgenler olacağını görmek kolaydır değil ise üst üste:

  • r2'nin sol kenarı, r1'in sağ kenarından daha sağdır

     ________     ________
    |        |   |        |
    |   r1   |   |   r2   |
    |        |   |        |
    |________|   |________|
  • veya r2'nin sağ kenarı, r1'in sol kenarından daha fazla sol

     ________     ________
    |        |   |        |
    |   r2   |   |   r1   |
    |        |   |        |
    |________|   |________|
  • veya r2’nin üst kenarı r1’in alt kenarının altında

     ________ 
    |        |
    |   r1   |
    |        |
    |________|
     ________ 
    |        |
    |   r2   |
    |        |
    |________|
  • veya r2'nin alt kenarı, r1'in üst kenarının üzerindedir.

     ________ 
    |        |
    |   r2   |
    |        |
    |________|
     ________ 
    |        |
    |   r1   |
    |        |
    |________|

Orijinal işlev - ve neden işe yaradığına dair alternatif bir açıklama burada bulunabilir: http://tekpool.wordpress.com/2006/10/11/rectangle-intersection-determine-if-two-given-rectangles-intersect- her-başka ya da olmayan /


1
Şaşırtıcı bir şekilde sezgiseldir ve cevabı bulmanın çok zor olduğu durumlarda karşıt bir sorunun cevabını bulmaya çalışmanın size yardımcı olabileceğini bir kez daha gösterir. Teşekkürler!
Lodewijk

1
Y ekseninin aşağıyı işaret ettiğini belirtmelisiniz (görüntüdeki gibi). Aksi takdirde, r2.top> r1.bottom ve r2.bottom <r1.top eşitsizliklerinin tersine çevrilmesi gerekir.
user1443778

@ user1443778 iyi yakalama! Ben de devam ettim ve bu algoritmanın arkasındaki mantığı, sistematik olmayan bir şekilde de koordine ettim.
Ponkadoodle

11

Nesne hizalı sınırlayıcı kutular istiyorsanız, bu öğreticiyi metanet tarafından ayırma ekseni teoreminde deneyin : http://www.metanetsoftware.com/technique/tutorialA.html

SAT en hızlı çözüm değil, ancak nispeten basit. Nesnelerinizi ayıracak tek bir çizgi (veya 3B ise bir düzlem) bulmaya çalışıyorsunuz. Bu çizgi varsa, kutularınızdan birinin kenarına paralellik yapılması garanti edilir, bu nedenle kutuları ayırıp ayırmadığını görmek için tüm kenar testlerini yineleyin.

Bu, yalnızca x / y eksenine sınırlayarak eksen hizalanmış kutular için de çalışır.


Dönmeyi düşünmüyordum, ama bu ilginç bir bağlantı.
Iain,

5

Yukarıdaki DoBoxesIntersect, iyi bir ikili çözümdür. Ancak, çok fazla kutunuz varsa, hala bir O (N ^ 2) sorununuz vardır ve bunun üzerine Kaj'ın ifade ettiği gibi bir şey yapmanız gerekebilir. (3D çarpışma saptama literatüründe, bunun hem geniş faz hem de dar faz algoritmasına sahip olduğu bilinir. Tüm olası çakışma çiftlerini bulmak için gerçekten hızlı bir şey yapacağız ve sonra mümkün olup olmadığını görmek için daha pahalı bir şey yapacağız çiftler gerçek çiftlerdir.)

Daha önce kullandığım geniş faz algoritması "süpürme ve erik"; 2D için, her bir kutunun başında ve sonunda iki sıralı liste tutmalısınız. Kutu hareketi çerçeveden çerçeveye >> ölçeklenmediği sürece, bu listelerin sırası fazla değişmeyecektir ve bu nedenle bunu korumak için balon veya ekleme sıralamasını kullanabilirsiniz. "Gerçek Zamanlı Render" kitabında yapabileceğiniz optimizasyonlar hakkında güzel bir yazı yazıyor, ancak K aşaması üst üste binen N kutuları için ve mükemmel gerçek dünya ile geniş aşamada O (N + K) zamanına indiriyor Hangi çift çiftlerin kareden çerçeveye kesiştiklerini takip etmek için N ^ 2 boolean'ları karşılayabiliyorsanız performans. Daha sonra genel olarak O (N + K ^ 2) zamana sahip olursunuz, çok fazla kutunuz varsa ancak sadece birkaç çakışma varsa, << O (N ^ 2).


5

Çok basit bir problem için matematiğin çok olduğu bir nokta, varsayalım, üst, sol, alt, sağ ...

2 çarpışmanın çarpışıp çarpışmadığını belirleme durumunda, bunlardan hiçbiri karşılanmazsa çarpışmayı önleyebilecek tüm olası aşırı uçların sadece sınır çakışmalarını eklemek istiyorsanız,>> uygun> = ve = <ile.

struct aRect{
  float top;
  float left;
  float bottom;
  float right;
};

bool rectCollision(rect a, rect b)
{
  return ! ( b.left > a.right || b.right < a.left || b.top < a.bottom || b.bottom > a.top);
}

Dürüst olmak gerekirse, bunun neden en çok oyu alan bir cevap olmadığından emin değilim. Basit, doğru ve verimli.
3Dave

3

ZorbaTHut'un cevabının alternatif versiyonu:

bool DoBoxesIntersect(Box a, Box b) {
     return (abs(a.x - b.x) < (a.width + b.width) / 2) &&
     (abs(a.y - b.y) < (a.height + b.height) / 2);
}

Aslında bu aritmetik her iki şekilde de iyi çalışıyor. Herhangi bir aritmetik işlemi <'nin her iki tarafına da yapabilirsiniz ve onu değiştirmez (negatif bir yöntemle çarpma, gerisinden daha azını değiştirmek zorundasınız). Bu örnekte, kutular çarpışmamalıdır. A kutusunun merkezi 1'de ise, -4'ten 6'ya kadar yayılırsa, Box b 10'da 10'a yayılır ve 7.5 ile 12.5 arasındadır, orada çarpışma olmaz ... Şimdi, Wallacoloo'nun yayınladığı yöntem doğru değil, ancak kısa devreleri uygulayan dillerde daha hızlı çalışacaktır, çünkü çoğu kontrol yine de yanlış geleceğinden, kısa devre kesildikten sonra kesilebilir
Deleter

Evet, bu sabah uyandığımda farkettim. Chris beni karıştırdı ve beni karıştırdı.
Iain

1
İki problem: ilk olarak, bölme çarpmadan çok daha yavaş olma eğilimindedir. İkincisi, eğer değerler tamsayı ise, bu bazı tamsayı kesme sorunlarına neden olabilir (ax = 0, bx = 9, a.width = 9, b.width = 10: abs (0-9) <(9 + 10) / 2, 9 <19/2, 9 <9, işlevler kutuların kesin olarak kesiştiği gerçeğine rağmen yanlış döndürür.)
ZorbaTHut

2

Çözmeye çalıştığınız soruna bağlı olarak, nesnelerinizi taşırken izlemekten daha iyi olabilirsiniz; yani, sıralı x başlangıç ​​ve bitiş konumlarının ve başlangıç ​​ve bitiş konumlarının bir listesini tutun. Bir sürü çakışma kontrolü yapmanız ve bu nedenle optimizasyon yapmanız gerekiyorsa, bunu kendi avantajınıza kullanabilirsiniz, derhal sol tarafınıza kimin kapandığını görebildiğiniz için, bitenin solundaki herkes budanabilir. hemen. Aynısı üst, alt ve sağ için de geçerlidir.
Defter tutma işlemi elbette zaman alır, bu nedenle hareketli nesnelerin az olduğu ancak çok sayıda çakışma kontrolü olan bir durum için daha uygundur.
Diğer bir seçenek de objeleri yaklaşık pozisyona göre kovaladığınız mekansal karmamadır (büyüklük onları çoklu kovalara koyabilir), ancak yine de, sadece, çok sayıda nesne varsa, defter tutma maliyeti nedeniyle yineleme başına nispeten az hareket eden.
Temel olarak (n * n) / 2 değerinden kaçınan herhangi bir şey (a nesnesini b a karşı kontrol ederseniz, b'yi açıkça kontrol etmek zorunda kalmazsınız), sınırlayıcı kutu kontrollerini optimize etmekten daha fazla yardımcı olur. Sınırlayıcı kutu kontrolleri bir tıkanıklıksa, soruna alternatif çözümler aramanızı şiddetle tavsiye ederim.


2

Merkezler arasındaki mesafe, köşeler arasındaki mesafeyle aynı değildir (örneğin bir kutu diğerinin içindeyken), yani GENEL, bu çözüm doğru olanıdır (benim düşünür).

merkezler arasındaki mesafe (örneğin, x için): abs(x1+1/2*w1 - x2+1/2*w2)veya1/2 * abs(2*(x1-x2)+(w1-w2)

Asgari mesafedir 1/2 w1 + 1/2 w2 or 1/2 (w1+w2). yarı yarıya iptal eder.

return 
ABS(2*(x1 - x2) + (w1-w2) ) < (w1+w2)) &&
ABS(2*(y1 - y2) + (h1-h2) ) < (h1+h2));

1
Oradaki "return" ifadesinde ne var?
doppelgreener 23:12

1

İşte benim Java uygulamasında iki tamamlayıcı bir mimariyi benimsemeye başlıyorum . İkili tamamlayıcı değilseniz, bunun yerine standart bir Math.abs işlev çağrısı kullanın:

boolean intersects(IntAxisAlignedBox left, IntAxisAlignedBox right) {
    return
        (
            lineDeltaFactor(left.min.x, left.max.x, right.min.x, right.max.x) |
            lineDeltaFactor(left.min.y, left.max.y, right.min.y, right.max.y) |
            lineDeltaFactor(left.min.z, left.max.z, right.min.z, right.max.z)
        ) == 0;
}

int lineDeltaFactor(int leftMin, int leftMax, int rightMin, int rightMax) {
    final int
            leftWidth = leftMax - leftMin,
            rightWidth = rightMax - rightMin,

            leftMid = leftMin + ((leftMax - leftMin) >> 1),
            rightMid = rightMin + ((rightMax - rightMin) >> 1);

    return (abs(leftMid - rightMid) << 1) / (leftWidth + rightWidth + 1);
}

int abs(int value) {
    final int mask = value >> (Integer.SIZE - 1);

    value ^= mask;
    value += mask & 1;
    return value;
}

Yarı düzgün bir derleyicinin / LLVM satırsonunun varsayılması, pahalı istifleme ve v-table aramalarını önlemek için bu işlevleri genişletir. Bu , 32-bit uç noktalara yakın (yani Integer.MAX_VALUEve Integer.MIN_VALUE) giriş değerleri için başarısız olur .


0

En hızlı yol, 4 değerin tümünü tek bir vektör yazmacında birleştirmektir.

Kutuları, aşağıdaki değerleri içeren bir vektörde saklayın [ min.x, min.y, -max.x, -max.y ]. Bu gibi kutuları saklarsanız, kesişme testi yalnızca 3 CPU talimatı alır:

_mm_shuffle_ps ikinci kutuyu döndürerek minimum ve maksimum yarıyı yeniden sıralamak için.

_mm_xor_ps_mm_set1_ps(-0.0f)ikinci kutuda tüm 4 değerin işaretlerini çevirmek için sihirli sayılarla .

_mm_cmple_ps 4 değeri de birbirleriyle karşılaştırmak, aşağıdaki iki kaydı karşılaştırmak için:

[ a.min.x, a.min.y, -a.max.x, -a.max.y ] < [ b.max.x, b.max.y, -b.min.x, -b.min.y ]

Son olarak, gerekirse _mm_movemask_ps, vektör ünitesinden bir skaler sicile çıktısı elde etmek için. 0 değeri, kesişen kutuları ifade eder. Veya 2'den fazla kutunuz varsa, bu gerekli değildir, değerleri vektör kaydında bırakın ve sonuçları birden fazla kutudan birleştirmek için bitsel işlemleri kullanın.

Dil veya platform belirtmediniz, ancak bunun gibi veya çok benzer bir şekilde SIMD desteği tüm platformlarda ve dillerde kullanılabilir. Cep telefonunda, ARM çok benzer şeylerle NEON SIMD'ye sahiptir. .NET, System.Runtime.Intrinsics ad alanında Vector128 öğesine sahiptir.

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.