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.
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.
Yanıtlar:
(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.
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.
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 /
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.
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).
Ç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);
}
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);
}
Çö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.
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));
İş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_VALUE
ve Integer.MIN_VALUE
) giriş değerleri için başarısız olur .
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.