Ekrandaki iki şeklin ya ortak bir noktası vardır ya da yoktur. Oyunlar, fizik simülasyonları, çizim programları ve her arayüzde bir tıklamanın hangi öğeye denk geldiğini bulan kod, bu soruyu her karede defalarca sorar. Çoğu zaman da yalnızca evet ya da hayır yetmez: Şekiller birbirinin içine ne kadar girmiş, çıkış hangi yönde?
Bu yazıda dört şekil çiftini ele alıyorum: kenarları eksenlere paralel iki kutu, iki daire, bir daireyle bir kutu ve ayırıcı eksen teoremiyle iki dışbükey çokgen, yani hiçbir yerinde içe doğru girinti olmayan iki çokgen. Bunlar için kullanılan klasik testlerin nereden geldiğini adım adım gösteriyorum. Her bölümde kurcalayabileceğiniz bir çizim var. Şekilleri sürükleyin ya da Tab tuşuyla birini seçip ok tuşlarıyla taşıyın. Çizimlerin altındaki sayıları, metinde anlatılan kodun kendisi hesaplıyor.
Ekran koordinatlarını kullanıyorum: sağa doğru, ise SVG’de ve HTML canvas’ta olduğu gibi aşağıya doğru artar. Uzunlukları çizimin kendi birimiyle ölçüyorum ve bu birimi px diye yazıyorum.
Bir çarpışma testi ne söyler?
Her şekli sınırıyla, yani dış çizgisiyle birlikte düşüneceğiz; matematikte buna kapalı küme denir. Böylece yalnızca sınırları buluşan iki şekil de kesişmiş sayılır; bu duruma temas diyeceğiz. Temas da bir çarpışmadır. Yerde duran bir nesne için fizik motorunun istediği de tam budur. Aşağıdaki her testin yanıtı üç durumdan biridir: Şekiller ya ayrıdır ya temas eder ya da örtüşür. Test bunun yanında şunları da bildirir:
- derinlik : şekillerin birbirinin içine ne kadar girdiği;
- normal : şeklinden şekline doğru bakan birim vektör, yani uzunluğu 1 olan bir yön;
- en kısa öteleme vektörü (İngilizce adının kısaltmasıyla MTV): iki şeklin yalnızca temas eder hâle gelmesi için ’nin kaydırılması gereken en kısa yol;
- örtüşmenin olduğu yerde bir temas noktası; şekiller ayrıysa aralarındaki uzaklık ve iki şeklin birbirine en yakın noktaları.
Daha kesin söyleyelim. ’nin her noktasını kadar kaydırınca elde edilen şekle diyelim. MTV, ile şekillerinin iç kısımlarında hiç ortak nokta bırakmayan en kısa vektörüdür. ’yı kadar kaydırmak da aynı işi görür. Fizik motorları bir adımda çoğu zaman iki cismi de yolun bir kısmı kadar kaydırır.
İki kutu: AABB ile AABB
Eksenlere hizalı sınırlayıcı kutu, kenarları eksenlere paralel olan bir dikdörtgendir; İngilizce adının kısaltmasıyla buna AABB denir. Matematik diliyle bu kutu kümesidir, yani koordinatı ile arasında, koordinatı ile arasında olan bütün noktalar. Test etmesi en kolay şekil budur. Son bölümde göreceğimiz gibi, ilk eleme de her yerde bu kutuyla yapılır.
Önce aralıklar
Bir doğru üzerinde iki kapalı aralık alalım: ve . Bu iki aralığın ortak noktası yoksa biri tamamen ötekinin solunda kalır, yani ya da olur. Tersi de doğru. Demek ki iki aralık şu koşul sağlanıyorsa kesişir, sağlanmıyorsa kesişmez:
Bir kutu, üzerindeki bir aralıkla üzerindeki bir aralığın çarpımıdır. İki çarpımın kesişimi de kesişimlerin çarpımına eşittir:
Çarpanlardan biri boşsa bu küme de boştur. Yani iki kutu, eksenine düşen gölgeleri örtüşüyorsa ve eksenine düşen gölgeleri de örtüşüyorsa çarpışır; biri bile ayrıysa çarpışmaz. Bunun için dört karşılaştırma yeter, toplama çıkarma bile gerekmez.
Ne kadar derin, çıkış hangi yönde?
Kutuları yönünde ayırmanın iki yolu var. sağa kayabilir: Sol kenarı ’nın sağ kenarına gelince durur, yani kadar yol alır. Ya da sola kayabilir: Sağ kenarı ’nın sol kenarına gelince durur, yani kadar yol alır. Bu iki yoldan kısa olanı, o eksendeki örtüşmedir:
pozitifse gölgeler örtüşüyordur, sıfırsa birbirine değiyordur. Negatifse aralarında genişliğinde bir boşluk vardır. Kutular ise, yani iki değerin küçüğü bile negatif değilse çarpışır. Derinlik olur ve MTV, bu en küçük değeri veren eksen boyunca uzanır. Kutular ayrıysa aralarındaki uzaklık iki boşluktan şöyle hesaplanır:
const overlapX = Math.min(a.max.x - b.min.x, b.max.x - a.min.x);
const overlapY = Math.min(a.max.y - b.min.y, b.max.y - a.min.y);
const hit = overlapX >= 0 && overlapY >= 0;
const depth = hit ? Math.min(overlapX, overlapY) : 0;
Bir noktaya dikkat: , gölgelerin ortak kısmının uzunluğu değil, gereken itmenin uzunluğudur. Kutulardan biri ötekinin içindeyse bu iki sayı farklı çıkar. Örneğin gölgesi aralığının içindeyse ikisinin ortak kısmı yalnızca 2 birimdir. Ama küçük gölgenin dışarı çıkması için 7 birim sağa ya da 5 birim sola gitmesi gerekir. Aşağıda küçük kutuyu büyüğün içine sürükleyin ve derinliğin, taralı örtüşme bölgesinin yüksekliğini nasıl geçtiğini izleyin.
Çarpışıyor — derinlik 25 px
- x ekseninde örtüşme
- 25 px
- y ekseninde örtüşme
- 95 px
- MTV
- (25, 0)
- Temas noktası
- (338, 223)
Bir şekli sürükleyin ya da Tab tuşuyla seçip ok tuşlarıyla 5 px taşıyın (Shift basılıyken 25 px).
Eksenlere hizalı iki kutu. Kutuların eksenine (altta) ve eksenine (solda) düşen gölgeleri çizimin kenarlarında görünüyor; A’nınki düz, B’ninki kesik çizgiyle. İki gölge çifti de örtüşüyorsa kutular çarpışır; biri bile ayrıysa çarpışmaz. Taralı dikdörtgen örtüşen bölge, ok ise MTV. Kesik çizgili çerçeve, B’nin bu ok kadar kaydırılınca duracağı yeri gösteriyor.
İki daire
Burada “daire” derken yalnızca çevre çizgisini değil, içiyle birlikte bütün diski kastediyorum: Merkezi , yarıçapı olan bir daire, noktasına uzaklığı en fazla olan bütün noktalardan oluşur. İki disk şu koşul sağlanıyorsa kesişir, sağlanmıyorsa kesişmez:
Yani merkezler arasındaki uzaklık, yarıçapların toplamını geçmemeli. ( işareti bir vektörün uzunluğunu gösterir.)
Diskler kesişiyorsa bu koşul sağlanır. Bu, üçgen eşitsizliğinden gelir: Bir üçgende bir kenar, öbür iki kenarın toplamından uzun olamaz. İki diskte birden bulunan bir noktası alırsak olur. Tersine, koşul sağlanıyorsa diskler kesişir. Bunu görmek için noktasından noktasına doğru yürüyün: noktasına uzaklığı olan nokta iki diskin de içindedir. Kodda karekök almaya gerek yok, kareleri karşılaştırmak yeter: dersek test olur. (, vektörünün uzunluğunun karesidir.)
Normal, iki merkezi birleştiren doğrunun yönüdür: . Yani vektörünü kendi uzunluğuna bölüp uzunluğu 1 olan bir vektör elde ediyoruz. Derinlik ise yarıçapların toplamının merkezler arasındaki uzaklığı ne kadar aştığıdır:
Bu, işe yarayan ötelemelerden herhangi biri değil, en kısası. Neden? bir vektörü kadar kaydırılırsa merkezler arasında en az kalmalıdır. olduğundan buradan çıkar. yönünde kaydırmak ise tam kadar yol ister. En derindeki iki nokta, üzerinde ve üzerinde noktalarıdır. Bu ikisinin tam ortası, temas noktası için doğal bir seçimdir.
Merkezleri birleştiren doğrunun hiç olmadığı bir durum var: eş merkezli daireler. O zaman hangi yönü seçsek aynıdır. Kod da sıfıra bölmek yerine birini seçer; burada bu yön .
Çarpışıyor — derinlik 19 px
- Merkezler arası uzaklık
- 156 px
- Yarıçapların toplamı
- 175 px
- MTV
- (19, −2)
- Temas noktası
- (325, 193)
Bir şekli sürükleyin ya da Tab tuşuyla seçip ok tuşlarıyla 5 px taşıyın (Shift basılıyken 25 px).
İki daire. Kesik çizgi merkezleri birleştiriyor; taralı, mercek biçimli bölge örtüşmeyi gösteriyor. Çizimin altında, karşılaştırabilmeniz için merkezler arasındaki uzaklık ve yarıçapların toplamı yan yana yazılı. Ok, yani MTV, merkezleri birleştiren doğru boyunca B’nin en derin noktasından A’nınkine uzanıyor.
Bir daireyle bir kutu
Bu iki farklı şekli birlikte test etmek için tek bir fikir yeter: Kutunun, dairenin merkezine en yakın noktasını bulun ve bu noktanın merkeze uzaklığını yarıçapla karşılaştırın. Uzaklığın karesi birbirinden bağımsız iki parçaya ayrılır,
kutunun koyduğu şartlar da iki koordinat için ayrıdır: değeri aralığında, değeri de aralığında kalmalıdır. Bu yüzden iki parçayı ayrı ayrı en küçük yapabiliriz. Bunun için merkezinin her koordinatını kendi aralığının içine çekmek, yani sınırlamak yeter. Koddaki adı clamp:
Yani bir sayı aralığın içindeyse olduğu gibi kalır, dışındaysa aralığın en yakın ucuna çekilir. Daire ile kutu, ise çarpışır, değilse çarpışmaz. Derinlik olur; normal de noktasından noktasına doğru bakar.
const p = { x: clamp(c.x, box.min.x, box.max.x), y: clamp(c.y, box.min.y, box.max.y) };
const dx = c.x - p.x;
const dy = c.y - p.y;
const hit = dx * dx + dy * dy <= r * r;
Kutunun kenarlarını uzatınca düzlem dokuz bölgeye ayrılır; çizimde bu çizgiler de var. Dairenin merkezi bir kenarın dışında ama o kenarın hizasındaysa koordinatlardan yalnızca biri sınırlanır ve o kenar boyunca kayar. Bir köşenin ötesindeyse iki koordinat da sınırlanır ve köşenin kendisi olur. Kutunun içindeyse hiçbir koordinat sınırlanmaz ve olur.
Aceleyle yazılmış kodun yanıldığı yer tam da bu son durumdur. olunca vektörü sıfır olur. Onu birim vektöre çevirmeye kalkarsanız sonuç NaN, yani geçersiz bir sayı olur. Geometriye bakarsak daire bir kenardan çıkmak zorundadır ve en kısa yol en yakın kenardan geçer. , , ve uzaklıklarının en küçüğüne dersek derinlik şudur:
Normal de o kenarın dışa bakan normalidir. Daha kısa bir itme işe yaramaz: Merkezin kutudan çıkması için en az yol alması, diskin tamamını dışarı taşımak için de ardından kadar daha gitmesi gerekir.
Çarpışıyor — derinlik 25 px
- En yakın nokta p
- (385, 145)
- B dairesinin merkezi
- kutunun dışında
- Uzaklık |c − p|
- 45 px
- MTV
- (23, −11)
Bir şekli sürükleyin ya da Tab tuşuyla seçip ok tuşlarıyla 5 px taşıyın (Shift basılıyken 25 px).
Bir kutu ve bir daire. Noktalı çizgiler kutunun kenarlarını uzatıyor. , kutuda dairenin merkezine en yakın nokta; koordinatlar sınırlanarak bulunuyor. Öbür durumu görmek için dairenin merkezini kutunun içine sürükleyin: En yakın kenar vurgulanır ve MTV daireyi o kenardan dışarı iter.
Ayırıcı eksen teoremi
Dönen kutular ve başka çokgenler için daha genel bir fikir gerekir. Kutu testine bir daha bakın: İki kutu, ya da ekseninde gölgeleri arasında boşluk kalıyorsa ayrıdır; kalmıyorsa çarpışır. Ayırıcı eksen teoremi (İngilizce adının kısaltmasıyla SAT), doğru eksenler denendiği sürece aynı fikrin herhangi iki dışbükey şekil için de işlediğini söyler.
Teorem. İki dışbükey çokgen ile ayrıksa, yani hiç ortak noktaları yoksa, öyle bir yönü vardır ki iki çokgenin bu yöndeki izdüşümleri de ayrık iki aralıktır. Tersi de doğrudur: Böyle bir yön varsa çokgenler ayrıktır. Bu yöne ayırıcı eksen denir. Bir şeklinin yönündeki izdüşümü, yani o yöne düşen gölgesi şu kümedir:
Üstelik her zaman ’nın ve ’nin kenar normalleri arasından seçilebilir.
Algoritma doğrudan buradan çıkar: İki çokgenin bütün kenar normallerini tek tek deneyin. Her normal için iki çokgenin de o eksendeki izdüşümünü alın ve çıkan iki aralığı karşılaştırın. Tek bir boşluk, şekillerin ayrı olduğunu kanıtlar. Hiçbir eksende boşluk yoksa şekiller kesişir.
İzdüşümler ve aday eksenler
iç çarpımı, noktasının doğrusal bir fonksiyonudur. Doğrusal bir fonksiyon, bir çokgenin üzerinde en küçük ve en büyük değerini köşelerde alır. Bu yüzden köşeleri olan bir çokgenin izdüşümü şöyle bulunur:
köşesinden köşesine giden bir kenarın yönü olsun. Bu kenara dik bir vektör, yani bir normal olarak alınabilir. Paralel kenarlar aynı ekseni verir; bu yüzden bir dikdörtgen iki, bir üçgen üç, düzgün bir altıgen de üç eksen getirir. Şekillerin çarpışıp çarpışmadığına karar vermek için normallerin uzunluğunun 1 olması gerekmez; ne kadar örtüştüklerini ölçmek içinse gerekir.
Kenar normalleri neden yeter?
En sade kanıt, Minkowski farkı denen kümeden geçer:
Bu küme, ’dan bir nokta, ’den bir nokta alıp birinciden ikinciyi çıkarınca elde edilebilecek bütün vektörlerden oluşur.
- ile ’nin ortak bir noktası olması, olmasıyla, yani sıfır vektörünün bu farkın içinde olmasıyla aynı şeydir. Çünkü ortak bir noktası verir.
- , ile kümelerinin Minkowski toplamıdır. İki dışbükey çokgenin Minkowski toplamı yine dışbükey bir çokgendir. ’nın ve ’nin kenarlarını yönlerine göre sıraya dizince bu çokgenin kenarları çıkar. Bu yüzden onun her kenar normali, ’nın ya da ’nin bir kenar normaliyle ya aynı yöndedir ya da tam ters yönde.
- Dışbükey bir çokgen, kenarlarının belirlediği yarı düzlemlerinin kesişimidir. (Yarı düzlem, bir doğrunun bir yanında kalan bütün noktalardır.) Burada kenarın dışa bakan normali, de ifadesinin çokgen üzerindeki en büyük değeridir. Bu yüzden noktasının çokgenin dışında kalması, olan bir kenar bulunmasıyla aynı şeydir.
- için bu en büyük değer şudur:
ise ’nın üzerindeki gölgesinin her noktası, ’nin gölgesinin her noktasından küçüktür; yani izdüşümler ayrıktır.
Hepsini birleştirelim: Çokgenler ayrıksa ’nın ya da ’nin kenar normallerinden biri onları ayırır. Öte yandan, hangi yönde olursa olsun tek bir ayırıcı eksen bulmak bile ayrık olduklarını kanıtlamaya yeter. ise şekiller birbirine değer: noktası kümesinin tam sınırındadır.
Minkowski farkı MTV’yi de hiç ek iş gerektirmeden verir. ’yi kadar kaydırmak, kümesini kadar kaydırır. Bu yüzden noktasını farkın dışına çıkaran en kısa ötelemenin boyu, noktasının farkın sınırına olan uzaklığı kadardır. noktasını içeren dışbükey bir çokgende bu uzaklık, en küçük değeridir. Her aday eksende yapılan hesap, kutulardaki “iki itmeden kısa olanı” hesabının aynısıdır; eksenin iki yönüne de bakılır:
Yani en az örtüşen ekseni seçmek, çoğu zaman işe yarayan bir kestirme değil: Normaller birim uzunluktaysa bu eksen derinliği tam olarak verir ve MTV onun boyunca uzanır.
/** İki dışbükey çokgenin derinliği; bir eksen onları ayırıyorsa null. */
function sat(a: Vec[], b: Vec[]): number | null {
let depth = Infinity;
for (const poly of [a, b]) {
for (let i = 0; i < poly.length; i++) {
const e = sub(poly[(i + 1) % poly.length], poly[i]);
const n = normalize({ x: e.y, y: -e.x });
const [minA, maxA] = project(a, n);
const [minB, maxB] = project(b, n);
const overlap = Math.min(maxA - minB, maxB - minA);
if (overlap < 0) return null; // ayırıcı eksen: çarpışma yok
depth = Math.min(depth, overlap);
}
}
return depth;
}
Aşağıdaki çizim bu döngünün daha kapsamlı bir sürümünü çalıştırıyor. İlk boşlukta durmak yerine bütün eksenleri saklıyor; böylece çizimin altındaki liste hepsini gösterebiliyor. Yönü, temas noktasını ve örtüşme bölgesini de hesaplıyor.
Çarpışıyor — derinlik 39 px
- Aday eksenler
- 5
- Ayırıcı eksen
- yok
- MTV
- (37, −10)
- Temas noktası
- (340, 208)
Her eksendeki izdüşümler
Çizimde: A2 (otomatik)
Çizimde görmek istediğiniz ekseni seçin; otomatik seçime dönmek için aynı ekseni yeniden seçin.
Bir şekli sürükleyin ya da Tab tuşuyla seçip ok tuşlarıyla 5 px taşıyın (Shift basılıyken 25 px). Q ve E tuşları seçili şekli 15° döndürür.
Döndürülmüş bir A dikdörtgeni ve dışbükey bir B çokgeni. Liste her aday ekseni iki gölgeyle gösteriyor: A’nınki dolu, B’ninki içi boş. Çizimde tek bir eksen, gölgeleri düşüren çizgilerle birlikte görünüyor: ayırıcı eksen varsa o, yoksa MTV’nin ekseni. Başka bir ekseni çizdirmek için listeden seçin. Şekilleri Q ve E tuşlarıyla ya da düğmelerle döndürebilirsiniz.
Denemeye değer birkaç şey var. B’yi, A’nın bir köşesine neredeyse değecek kadar yaklaştırın ama değdirmeyin: Çoğu zaman boşluğu yalnızca B’nin eksenlerinden biri bulur; A’nın kendi eksenlerinde gölgeler hâlâ örtüşür. B için dikdörtgeni seçin ve kenarları A’nınkilerle aynı hizaya gelene kadar döndürün: Eksenleri A’nınkilere paralel olur ve liste bunların yeni bir şey katmadığını gösterir. Beşgeni seçince yedi aday eksen olur ve derinlik her zaman bunlar arasındaki en küçük örtüşmedir.
Döndürülmüş dikdörtgenler ve üçgenler
Merkezi , yarı genişliği , yarı yüksekliği ve açısı olan döndürülmüş bir dikdörtgenin köşeleri şunlardır:
Dikdörtgenin iki ekseni matrisinin sütunlarıdır: ve . Yani bu eksenleri kenarlardan hesaplamaya hiç gerek yok. Dikdörtgenin birim uzunluktaki bir eksenine izdüşümü, merkezi , yarı uzunluğu olan bir aralıktır. Böylece dört köşenin izdüşümünü tek tek almaya gerek kalmaz. İki döndürülmüş dikdörtgen için dört eksen gerekir. Eksenlere hizalı kutu, olan özel durumdur; o zaman dört eksen, bildiğimiz iki eksene iner.
Bir üçgeni üç eksen getirir. vektörel çarpımının işareti, üçgenin köşelerinin saat yönünde mi tersine mi sıralandığını söyler. normalinin üçgenin dışına mı içine mi baktığını da bu sıra belirler. SAT testinin kendisi için bunun önemi yok, çünkü ekseni ters çevirince iki aralık da aynadaki gibi ters döner ve örtüşüp örtüşmedikleri değişmez. Ama dışa bakan normallerle MTV’nin yönünü düşünmek daha kolaydır. Aynı vektörel çarpım, bir noktasının üçgenin içinde olup olmadığını da söyler. Bunun için çarpımı , ve alt üçgenlerine uygularız: Üç işaretten hiçbiri bütün üçgeninkinin tersi değilse nokta içeridedir.
Dışbükeylik neden önemli?
Kanıtta dışbükeyliği iki yerde kullandık. 2. adımda kümesinin dışbükey bir çokgen çıkmasına, 3. adımda da dışbükey bir çokgenin dışındaki her noktanın kenar yarı düzlemlerinden birinin dışında kalmasına dayandık. İçbükey, yani bir yerinde içe doğru girinti olan şekillerde bu sonuç geçerli değildir. Aşağıdaki çizimde küçük bir kare, U biçimli bir şeklin oyuğunda, ona değmeden duruyor. Her aday eksende gölgeler örtüşüyor, bu yüzden SAT çarpışma bildiriyor. Sorun şu: Şekiller birbirine değmese de aralarından düz bir çizgi geçirilemez, çünkü aradaki boşluk oyuğun içinde kalıyor.
SAT: çarpışıyor — yanlış, şekiller arasında 25 px boşluk var
- SAT (U’nun tamamı)
- çarpışıyor
- Dışbükey parçalar
- çarpışma yok
- Uzaklık
- 25 px
- Ayırıcı eksen
- yok
Bir şekli sürükleyin ya da Tab tuşuyla seçip ok tuşlarıyla 5 px taşıyın (Shift basılıyken 25 px).
İçbükey bir şekle uygulanan SAT. U’nun kenarları yalnızca iki yönde uzanıyor: yatay ve dikey. İki eksende de U’nun ve karenin gölgeleri örtüştüğü için SAT çarpışma bildiriyor. Kesik çizgiler U’yu üç dışbükey parçaya bölüyor. Kareyi her parçayla ayrı ayrı test edince doğru yanıt çıkıyor.
Bu sorunun olağan çözümü, içbükey çokgeni gibi dışbükey parçalara bölmek ve her parçayı ayrı ayrı test etmektir. Örneğin çokgeni kulak kesme (ear clipping) yöntemiyle üçgenlere ya da bir dışbükey ayrıştırmayla daha az sayıda parçaya bölebilirsiniz. Parçalardan biri çarpışıyorsa şekiller çarpışır; hiçbiri çarpışmıyorsa çarpışmaz. Derinlik ise daha fazla dikkat ister. Parçaların derinliklerinin en büyüğü, bütün şeklin derinliği için yalnızca bir alt sınırdır; yani gerçek derinlik bundan az olamaz ama daha fazla olabilir. Çünkü ’yi bir parçanın dışına iten hareket, onu doğrudan başka bir parçanın içine sokabilir. Kesin MTV için parçaları birlikte düşünmek gerekir: MTV yine noktasından sınıra giden en kısa yoldur, ama bu kez sınır, Minkowski farklarının birleşiminin sınırıdır.
Hesap ne kadar sürer?
İlk üç test sabit zamanda çalışır, yani her seferinde aynı, az sayıda işlem yapar: iki kutu için dört karşılaştırma; iki daire için uzaklığın karesiyle yarıçaplar toplamının karesi arasında tek bir karşılaştırma; bir daireyle bir kutu için iki sınırlama ve bir uzaklık karesi.
Köşe sayıları ve olan iki çokgende SAT, en fazla eksenin her biri için köşenin izdüşümünü alır. Bu, iş demektir, yani iş köşe sayısının karesiyle büyür. İlk boşluk bulununca da döngüden hemen çıkılır. Sıradan bir oyundaki küçük çokgenler için bu hızlıdır. Çok köşeli şekillerde ya da 3B’de ise GJK (Gilbert–Johnson–Keerthi) gibi yinelemeli algoritmalar daha iyi ölçeklenir. GJK, Minkowski farkıyla destek noktaları üzerinden çalışır; derinliği bulmak için yanına EPA (expanding polytope algorithm) eklenir.
Asıl maliyet ise çiftlerin sayısından gelir. şekilli bir sahnede çift vardır: bin şekil için her karede 499.500 çift.
Uygulamada
Önce kaba eleme, sonra kesin test
Gerçek sistemler işi ikiye böler. Önce kaba eleme aşaması (İngilizce kaynaklarda broad phase) çiftlerin çoğunu ucuz bir yoldan ayıklar. Bunu temkinli yapar, yani çarpışabilecek hiçbir çifti atmaz. Bunun için her şekli eksenlere hizalı bir kutuya sarar: döndürülmüş bir dikdörtgeni dört köşesini içine alan kutuya, bir daireyi kenarı olan kareye. Sonra örtüşen kutuları uzamsal bir veri yapısıyla bulur. Bu iş için eşit hücreli bir ızgara, kutuları tek eksen boyunca sıralayıp tarayan bir yöntem (sort and sweep) ya da bir sınırlayıcı hacim hiyerarşisi (BVH) kullanılabilir. Yalnızca bu elemeyi geçen adaylar yukarıdaki kesin testlere ulaşır; bu ikinci aşamaya kesin test aşaması (narrow phase) denir. Böylece en basit test olan kutu testi iki ayrı yerde iş görür.
Kayan nokta hatalarına pay bırakmak
Kâğıt üzerinde tam olarak birbirine değen şekiller, hesap kayan noktalı sayılarla yapılınca nadiren tam olarak değer. IEEE çift duyarlıklı sayılarda (double) 0.1 + 0.2 işlemi 0.30000000000000004 verir; 0.1 sayısını on kez toplamak da 0.9999999999999999 eder. Bir dikdörtgeni kadar, yani 90 derece döndürürken yerine değeri kullanılır. Bu yüzden doğrusuna oturması gereken bir köşe 1.9999999999999998 konumuna düşer ve birbirine değmesi gereken iki dikdörtgen yaklaşık kadar örtüşür. Buradan birkaç pratik kural çıkıyor:
- Temas durumunu bir toleransla tanımlayın: içinde kalan, yani sıfıra çok yakın bir örtüşme temas sayılsın. Bu yazıdaki çizimler px kullanıyor; bu, gözün ancak ayırt edebildiği kadar bir fark. Genel kullanım içinse çizimlerin arkasındaki kod, varsayılan değerini koordinatların büyüklüğüne göre ölçekliyor, çünkü yuvarlama hataları sayılar büyüdükçe artar.
- Yalnızca evet ya da hayır yanıtı gerekiyorsa uzunlukların karelerini karşılaştırın. Vektörleri ancak bir derinlik ya da yön gerektiğinde birim vektöre çevirin.
- Sıfır olabilecek bir uzunluğa yapılan her bölmeyi önceden kontrol edin: eş merkezli daireler, kutunun içine düşen bir merkez, uzunluğu sıfır olan kenarlar, bir doğru parçasına ya da tek bir noktaya dönüşen çokgenler.
Çarpışan şekilleri ayırmak
En basit çözüm MTV’dir: Şekillerden birini bu vektör kadar kaydırın ya da kaydırmayı iki şekil arasında kütleleriyle orantılı olarak paylaştırın. Bir fizik motoru normale de ihtiyaç duyar; hızın öteki cisme doğru olan bileşenini onunla sıfırlar. Bir tuzak daha var: Hızlı ve küçük bir nesne, iki kare arasında ince bir nesnenin içinden geçip gidebilir ve hiçbir karede onunla örtüşmüş görünmez. Buna tünelleme denir. Sürekli çarpışma tespiti bunu önler: Şeklin hareket ederken taradığı alanı test eder ya da çarpışmanın tam anını hesaplar. Zaman adımlarını küçültmek de işe yarar.
Özet
| Çift | Ne zaman çarpışır? | Derinlik | Maliyet |
|---|---|---|---|
| Kutu ve kutu | gölgeler hem hem ekseninde örtüşüyorsa | ||
| Daire ve daire | ise | ||
| Daire ve kutu | ise (: sınırlanmış nokta) | ; merkez içerideyse | |
| Dışbükey çokgenler | hiçbir kenar normali gölgeleri ayırmıyorsa | kenar normallerindeki en küçük örtüşme |
Her test, aynı fikrin daha özel ya da daha genel bir hâli: Şekillerin gölgesini, soruyu tek boyuta indiren bir eksene düşürün ve bir boşluk arayın. Çizimlerin arkasındaki kod, burada konuştuğumuz uç durumların testleriyle birlikte sitenin deposunda, src/lib/geometry klasöründe duruyor. Bu uç durumlar şunlar: birbirine değen şekiller, iç içe şekiller, bir çizgiye ya da noktaya dönüşen çokgenler ve kadar döndürme.