Toptan Topa Çarpışma - Tespit ve Elleçleme


266

Stack Overflow topluluğunun yardımıyla oldukça basit ama eğlenceli bir fizik simülatörü yazdım.

alternatif metin

Bir topu başlatmak için fareyi tıklayıp sürükleyin. Etrafta zıplayacak ve sonunda "katta" duracaktır.

Eklemek istediğim bir sonraki büyük özellik, top çarpışması. Topun hareketi balta ve y hız vektörüne bölünür. Yerçekimim var (her adımda y vektörünün küçük azalması), sürtünme var (her çarpışmada bir duvarla her iki vektörün küçük bir azalması). Toplar dürüstçe şaşırtıcı derecede gerçekçi bir şekilde hareket eder.

Sanırım sorum iki bölümden oluşuyor:

  1. Toptan topa çarpışmayı tespit etmenin en iyi yöntemi nedir?
    Sadece her topun üzerinde tekrarlayan ve diğer tüm topların yarıçapla çakışıp çakışmadığını kontrol eden bir O (n ^ 2) döngüm var mı?
  2. Topla çarpışmaya karşı hangi denklemleri kullanırım? Fizik 101
    İki topun hız x / y vektörlerini nasıl etkiler? İki topun yöneldiği yön nedir? Bunu her top için nasıl uygularım?

alternatif metin

"Duvarlar" ın çarpışma tespiti ve sonuçta meydana gelen vektör değişiklikleri kolaydı ancak top-top çarpışmalarında daha fazla komplikasyon görüyorum. Duvarlarda sadece uygun x veya y vektörünün negatifini almak zorunda kaldım ve kapalı doğru yönde ilerleyecekti. Toplarla bu şekilde olduğunu sanmıyorum.

Bazı hızlı açıklamalar: Basitlik için şimdilik mükemmel elastik bir çarpışma ile iyiyim, ayrıca tüm toplarım şu anda aynı kütleye sahip, ancak gelecekte bunu değiştirebilirim.


Düzenleme: Yararlı bulduğum kaynaklar

Vektörlerle 2d Top fiziği: Trigonometri Olmadan 2 Boyutlu Çarpışmalar.pdf
2d Top çarpışma algılama örneği: Çarpışma Tespiti Ekleme


Başarı!

Top çarpışma tespiti ve yanıtı harika çalışıyor!

İlgili kod:

Çarpışma algılama:

for (int i = 0; i < ballCount; i++)  
{  
    for (int j = i + 1; j < ballCount; j++)  
    {  
        if (balls[i].colliding(balls[j]))  
        {
            balls[i].resolveCollision(balls[j]);
        }
    }
}

Bu, her top arasındaki çarpışmaları kontrol eder, ancak gereksiz kontrolleri atlar (eğer top 1'in top 2 ile çarpışıp çarpışmadığını kontrol etmek zorunda kalırsanız, top 2'nin top 1 ile çarpışıp çarpışmadığını kontrol etmeniz gerekmez. Ayrıca, kendisiyle çarpışma kontrolünü atlar. ).

Sonra, benim top sınıfta benim colliding () ve resolCollision () yöntemleri var:

public boolean colliding(Ball ball)
{
    float xd = position.getX() - ball.position.getX();
    float yd = position.getY() - ball.position.getY();

    float sumRadius = getRadius() + ball.getRadius();
    float sqrRadius = sumRadius * sumRadius;

    float distSqr = (xd * xd) + (yd * yd);

    if (distSqr <= sqrRadius)
    {
        return true;
    }

    return false;
}

public void resolveCollision(Ball ball)
{
    // get the mtd
    Vector2d delta = (position.subtract(ball.position));
    float d = delta.getLength();
    // minimum translation distance to push balls apart after intersecting
    Vector2d mtd = delta.multiply(((getRadius() + ball.getRadius())-d)/d); 


    // resolve intersection --
    // inverse mass quantities
    float im1 = 1 / getMass(); 
    float im2 = 1 / ball.getMass();

    // push-pull them apart based off their mass
    position = position.add(mtd.multiply(im1 / (im1 + im2)));
    ball.position = ball.position.subtract(mtd.multiply(im2 / (im1 + im2)));

    // impact speed
    Vector2d v = (this.velocity.subtract(ball.velocity));
    float vn = v.dot(mtd.normalize());

    // sphere intersecting but moving away from each other already
    if (vn > 0.0f) return;

    // collision impulse
    float i = (-(1.0f + Constants.restitution) * vn) / (im1 + im2);
    Vector2d impulse = mtd.normalize().multiply(i);

    // change in momentum
    this.velocity = this.velocity.add(impulse.multiply(im1));
    ball.velocity = ball.velocity.subtract(impulse.multiply(im2));

}

Kaynak Kodu: Top-top çarpıştırıcı için tam kaynak.

Herkes bu temel fizik simülatörü geliştirmek için bazı öneriler varsa bana bildirin! Henüz eklemem gereken bir şey açısal momentum, böylece toplar daha gerçekçi bir şekilde yuvarlanacak. Başka öneriniz var mı? Yorum Yap!


16
Bu algoritmanın yeterince iyi olduğunu düşünmüyorum çünkü toplarınız çok hızlı hareket ediyorsa (örn: çerçeve başına 2 * yarıçaptan daha hızlı, bir top herhangi bir çarpışma olmadan başka bir toptan geçebilir.
Benji Mizrahi

@Simulcal, kaynak kodunuzu tekrar yükleyebilir misiniz (tüm filedropper.com bağlantıları bozuk görünüyor). Ayrıca [geocities.com/vobarian/2dcollisions/2dcollisions.pdf]
koyabilirsiniz

1
Burada çalıştığım BallBounce'un son sürümüne bir link var: dl.dropbox.com/u/638285/ballbounce.rar
mmcdole

@ Katkıda bulunan herkese: Bu motoru 3D'ye dönüştürmek için biraz ışık tutabilir misiniz? Bu harika motorun Java3D'de nasıl çalışabileceği.
statik boşluk ana

2
Çizgi Vector2d impulse = mtd.multiply(i);normalleştirilmiş mtd vektörü olmalıdır. Gibi bir şey:Vector2d impulse = mtd.normalize().multiply(i);
klenwell

Yanıtlar:


117

İki topun çarpışıp çarpışmadığını tespit etmek için, merkezleri arasındaki mesafenin yarıçapın iki katından az olup olmadığını kontrol edin. Toplar arasında mükemmel bir elastik çarpışma yapmak için, sadece çarpışma yönünde olan hızın bileşeni hakkında endişelenmeniz gerekir. Diğer bileşen (çarpışmaya teğet) her iki top için de aynı kalacaktır. Bir toptan diğerine yönünü gösteren bir birim vektörü oluşturarak ve ardından nokta ürününü topların hız vektörleriyle alarak çarpışma bileşenlerini elde edebilirsiniz. Daha sonra bu bileşenleri 1D mükemmel elastik çarpışma denklemine takabilirsiniz.

Wikipedia, tüm sürecin oldukça iyi bir özetine sahiptir . Herhangi bir kütlenin bilyaları için, yeni hızlar denklemler kullanılarak hesaplanabilir (burada v1 ve v2 çarpışmadan sonraki hızlardır ve u1, u2 öncekinden gelir):

v_ {1} = \ frac {u_ {1} (m_ {1} -m_ {2}) + 2m_ {2} u_ {2}} {m_ {1} + m_ {2}}

v_ {2} = \ frac {u_ {2} (m_ {2} -m_ {1}) + 2m_ {1} u_ {1}} {m_ {1} + m_ {2}}

Toplar aynı kütleye sahipse, hızlar basitçe değiştirilir. İşte benzer bir şey yapar yazdım bazı kod:

void Simulation::collide(Storage::Iterator a, Storage::Iterator b)
{
    // Check whether there actually was a collision
    if (a == b)
        return;

    Vector collision = a.position() - b.position();
    double distance = collision.length();
    if (distance == 0.0) {              // hack to avoid div by zero
        collision = Vector(1.0, 0.0);
        distance = 1.0;
    }
    if (distance > 1.0)
        return;

    // Get the components of the velocity vectors which are parallel to the collision.
    // The perpendicular component remains the same for both fish
    collision = collision / distance;
    double aci = a.velocity().dot(collision);
    double bci = b.velocity().dot(collision);

    // Solve for the new velocities using the 1-dimensional elastic collision equations.
    // Turns out it's really simple when the masses are the same.
    double acf = bci;
    double bcf = aci;

    // Replace the collision velocity components with the new ones
    a.velocity() += (acf - aci) * collision;
    b.velocity() += (bcf - bci) * collision;
}

Verimlilik konusunda Ryan Fox haklı, bölgeyi bölümlere ayırmayı ve sonra her bölüm içinde çarpışma tespiti yapmayı düşünmelisiniz. Topların bir bölümün sınırlarındaki diğer toplarla çarpışabileceğini unutmayın, bu da kodunuzu çok daha karmaşık hale getirebilir. Yine de birkaç yüz topunuz olana kadar verimlilik muhtemelen önemli olmayacaktır. Bonus puanları için, her bölümü farklı bir çekirdek üzerinde çalıştırabilir veya her bölümdeki çarpışma işlemlerini bölebilirsiniz.


2
Diyelim ki iki topun kütleleri eşit değil. Bu, toplar arasındaki vektör değişimini nasıl etkiler?
mmcdole

3
12. sınıftan bu yana bir süre geçti, ama sanırım kütlelerin oranına karşılık gelen bir momentum oranı elde ettiler.
Ryan Fox

6
@Jay, sadece şunu belirtmek için .. eklediğiniz bir denklem görüntüsünün 2 boyutlu değil, 1 boyutlu bir çarpışma için olduğunu.
mmcdole

@simucal. doğru değil ... u ve v bu denklemdeki vektörlerdir. Yani x, y (ve z) bileşenleri vardır.
Andrew Rollings

2
@Simucal, haklısın, onlar tek boyutlu durum için. Daha fazla boyut için, sadece çarpışmaya uygun hız bileşenlerini kullanın (aci, kodda bci). Diğer bileşenler çarpışmaya diktir ve değişmez, bu yüzden onlar için endişelenmenize gerek yoktur.
Jay Conrod

48

Yıllar önce programı burada sunduğunuz gibi yaptım.
Tek bir gizli sorun var (ya da birçoğu, bakış açısına bağlı olarak):

  • Topun hızı çok yüksekse, çarpışmayı kaçırabilirsiniz.

Ayrıca, neredeyse% 100 durumda yeni hızlarınız yanlış olacaktır. Hızlar değil , konumlar . Yeni hızları tam olarak doğru yerde hesaplamanız gerekir . Aksi takdirde, topları önceki ayrık adımda bulunan küçük bir "hata" miktarında kaydırırsınız.

Çözüm açıktır: zaman adımını bölmelisiniz, böylece önce doğru yere kaydırın, sonra çarpışın, sonra kalan zamanınız için kaydırın.


Vites konumları açıksa timeframelength*speed/2, pozisyonlar istatistiksel olarak sabitlenir.
Nakilon

@Nakilon: hayır, sadece bazı durumlarda yardımcı olur, ancak genellikle çarpışmayı kaçırmak mümkündür. Ve çarpışmayı kaçırma olasılığı, zaman diliminin uzunluğuyla artar. Bu arada, Aleph doğru çözümü gösterdi gibi görünüyor (ben sadece bunu yağmaladım).
avp

1
@avp, Ben değildim Topun hızı çok yüksekse, çarpışmayı kaçırabilirsiniz. , ancak yeni pozisyonlarınız hakkında yanlış olacaktır . Çarpışma biraz daha geç tespit edildiklerinden, gerçekten çarpıştıkları zamandan daha fazla tespit edilirse timeframelength*speed/2, bu pozisyondan soyutlama yapılırsa doğruluk iki kat artar.
Nakilon

20

Bu sorunu çözmek için boşluk bölümleme kullanmalısınız.

Haberlerini okuyun İkili Uzay Partisyonlama'ya ve dörtlü ağaçlar


4
Alan bölümleme yerine, süpürme ve kurulama algoritması daha iyi çalışmaz mı? Toplar hızlı hareket ediyor, bu nedenle herhangi bir bölümleme sık sık güncellenmeli ve yukarıdan çıkmalı. Bir süpürme ve kuru erik O (n log n) 'deki tüm çarpışan çiftleri geçici veri yapıları olmadan bulabilir. İşte temeller için iyi bir öğretici
HugoRune

13

Ryan Fox'un ekranı bölgelere bölme ve sadece bölgelerdeki çarpışmaları kontrol etme önerisine bir açıklama olarak ...

örneğin, oyun alanını bir kareler ızgarasına ayırın (keyfi olarak her tarafta 1 birim uzunluğunda olduğunu söyleyecektir) ve her ızgara karesinde çarpışma olup olmadığını kontrol edin.

Bu kesinlikle doğru çözüm. Bununla ilgili tek sorun (başka bir afişin işaret ettiği gibi), sınırlar arası çarpışmaların bir sorun olduğudur.

Bunun çözümü, ikinci bir ızgarayı 0,5 birim dikey ve yatay ofsette birinciye yerleştirmektir.

Daha sonra, ilk ızgaradaki (ve dolayısıyla algılanmayan) sınırlar ötesinde olabilecek çarpışmalar ikinci ızgaradaki ızgara kareleri içinde olacaktır. Daha önce ele aldığınız çarpışmaların kaydını tuttuğunuz sürece (bazı çakışmalar olması muhtemeldir), uç vakaları ele alma konusunda endişelenmenize gerek yoktur. Tüm çarpışmalar, ızgaralardan birinin üzerindeki ızgara karesinde olacaktır.


Daha doğru bir çözüm için +1 ve korkak sürüşlü downvoter'a karşı
Steven A. Lowe

1
bu iyi bir fikir. Bunu bir kez yaptım ve mevcut hücreyi ve tüm komşu hücreleri kontrol ettim, ancak yönteminiz daha verimli. Düşündüğüm bir başka yol, geçerli hücreyi kontrol etmek ve sonra da geçerli hücre sınırlarıyla kesişip kesişmediğini kontrol etmek ve eğer öyleyse, THAT komşu hücredeki nesneleri kontrol etmektir.
LoveMeSomeCode

10

Çarpışma kontrolü sayısını azaltmanın iyi bir yolu, ekranı farklı bölümlere ayırmaktır. Daha sonra her topu sadece aynı bölümdeki toplarla karşılaştırırsınız.


5
Düzeltme: aynı VE bitişik bölümlere sahip çarpışmalar olup olmadığını kontrol etmeniz gerekir
rint

7

Burada optimize etmek için gördüğüm bir şey var.

Mesafe yarıçaplarının toplamı olduğunda topların çarptığını kabul ederken, asla bu mesafeyi hesaplamamalıyız! Aksine, karesini hesaplayın ve onunla bu şekilde çalışın. Bu pahalı karekök operasyonunun bir nedeni yok.

Ayrıca, bir çarpışma bulduğunuzda, artık kalmayana kadar çarpışmaları değerlendirmeye devam etmeniz gerekir. Sorun, ilkinin doğru bir resim almadan önce çözülmesi gereken diğerlerine neden olabilmesidir. Top kenardan topa vurursa ne olacağını düşünün? İkinci top kenara çarpar ve hemen ilk topa geri döner. Köşedeki bir top yığınına çarparsanız, bir sonraki döngüyü tekrarlayabilmeniz için çözülmesi gereken birkaç çarpışma olabilir.

O (n ^ 2) gelince, tek yapabileceğiniz eksik olanları reddetme maliyetini en aza indirmektir:

1) Hareket etmeyen bir top hiçbir şeye çarpamaz. Yerde yatan makul sayıda top varsa, bu çok fazla testten tasarruf edebilir. (Yine de bir şeyin sabit topa vurup vurmadığını kontrol etmeniz gerektiğini unutmayın.)

2) Yapmaya değer bir şey: Ekranı birkaç bölgeye ayırın, ancak çizgiler bulanık olmalıdır - bir bölgenin kenarındaki toplar tüm ilgili (4 olabilir) bölgede olarak listelenir. 4x4 ızgara kullanır, bölgeleri bit olarak saklarım. İki top bölgesinin bölgelerinin AND değeri sıfır döndürürse, testin sonu.

3) Dediğim gibi, kare kökü yapmayın.


Karekök ucundaki bilgiler için teşekkür ederiz. Kareye kıyasla pahalı doğasını bilmiyordum.
mmcdole

Başka bir optimizasyon, başka hiçbir topun yakınında olmayan topları bulmak olacaktır. Bu, sadece topların hızları kısıtlandığında güvenilir bir şekilde çalışacaktır.
Brad Gilbert

1
İzole topları aramaya katılmıyorum. Bu, çarpışmayı tespit etmek kadar pahalı. İşleri iyileştirmek için söz konusu top için O (n) 'den daha az bir şeye ihtiyacınız vardır.
Loren Pechtel

7

Çarpışma tespiti ve 2B'de yanıt hakkında mükemmel bir sayfa buldum.

http://www.metanetsoftware.com/technique.html

Bunun nasıl yapıldığını akademik açıdan anlatmaya çalışıyorlar. Basit nesneden nesneye çarpışma tespiti ile başlarlar ve çarpışma tepkisine ve bunun nasıl ölçekleneceğine devam ederler.

Düzenle: Güncel bağlantı


3

Bunu yapmanın iki kolay yolu var. Jay topun ortasından doğru bir şekilde kontrol etti.

Daha kolay bir yol, bir dikdörtgen sınırlayıcı kutu kullanmak, kutunuzun boyutunu topun% 80'i olacak şekilde ayarlamaktır ve çarpışmayı oldukça iyi simüle edersiniz.

Top sınıfınıza bir yöntem ekleyin:

public Rectangle getBoundingRect()
{
   int ballHeight = (int)Ball.Height * 0.80f;
   int ballWidth = (int)Ball.Width * 0.80f;
   int x = Ball.X - ballWidth / 2;
   int y = Ball.Y - ballHeight / 2;

   return new Rectangle(x,y,ballHeight,ballWidth);
}

Ardından, döngünüzde:

// Checks every ball against every other ball. 
// For best results, split it into quadrants like Ryan suggested. 
// I didn't do that for simplicity here.
for (int i = 0; i < balls.count; i++)
{
    Rectangle r1 = balls[i].getBoundingRect();

    for (int k = 0; k < balls.count; k++)
    {

        if (balls[i] != balls[k])
        {
            Rectangle r2 = balls[k].getBoundingRect();

            if (r1.Intersects(r2))
            {
                 // balls[i] collided with balls[k]
            }
        }
    }
}

1
Bu, topların yatay ve dikey çarpışmalarda% 20 birbirlerine girmesini sağlar. Verimlilik farkı göz ardı edilebilir olduğundan dairesel sınırlama kutuları da kullanılabilir. Ayrıca (x-width)/2olmalı x-width/2.
Markus Jarderot

Öncelik yazım hatası için iyi bir çağrı. Çoğu 2d oyununun, dikdörtgen olmayan şekiller üzerinde dikdörtgen sınırlama kutuları kullandığını göreceksiniz çünkü hızlıdır ve kullanıcı neredeyse hiçbir zaman fark etmemektedir.
FlySwat

Dikdörtgen sınırlama kutusu yapabilirsiniz, o zaman bir vuruş varsa dairesel sınırlama kutusunu kontrol edin.
Brad Gilbert

1
@Jonathan Holland, iç döngünüz için olmalıdır (int k = i + 1; ...) Bu gereksiz denetimlerden kurtulacaktır. (yani kendiliğinden çarpışma ile kontrol ve çarpışma topu1 ile ball2 sonra ball2 ile ball1 kontrol edilir).
mmcdole

4
Aslında, bir kare sınırlama kutusunun, dairesel bir sınırlama kutusundan daha kötü performans göstermesi muhtemeldir (kare kökünü optimize ettiğiniz varsayılarak)
Ponkadoodle

3

Burada ve orada ima ettiğini görüyorum, ancak önce daha hızlı bir hesaplama da yapabilirsiniz, örneğin, üst üste binme için sınırlayıcı kutuları karşılaştırırsınız ve sonra ilk test geçerse yarıçap tabanlı bir çakışma yapabilirsiniz.

Sınırlama kutusu için toplama / fark matematiği yarıçap için tüm tetiklemelere göre çok daha hızlıdır ve çoğu zaman sınırlama kutusu testi çarpışma olasılığını ortadan kaldırır. Ancak daha sonra trig ile tekrar test ederseniz, aradığınız doğru sonuçları elde edersiniz.

Evet, iki test, ama genel olarak daha hızlı olacak.


6
Trigere ihtiyacınız yok. bool is_overlapping(int x1, int y1, int r1, int x2, int y2, int r2) { return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)<(r1+r2)*(r1+r2); }
Ponkadoodle


2

Bu kodu HTML Canvas öğesini kullanarak JavaScript'te uyguladım ve saniyede 60 kare hızında harika simülasyonlar üretti. Simülasyona rastgele pozisyonlarda ve hızlarda bir düzine top koleksiyonuyla başladım. Daha yüksek hızlarda, küçük bir top ve çok daha büyük bir top arasındaki bir çarpışma, küçük topun daha büyük topun kenarına STICK görünmesine neden olduğunu ve ayrılmadan önce büyük topun etrafında 90 dereceye kadar hareket ettiğini gördüm. (Başka birinin bu davranışı gözlemlediğini merak ediyorum.)

Hesaplamaların bazı kayıtları, bu durumlarda Minimum Çeviri Mesafesinin, bir sonraki seferde aynı topların çarpışmasını önleyecek kadar büyük olmadığını göstermiştir. Bazı deneyler yaptım ve MTD'yi göreceli hızlara göre ölçeklendirerek bu sorunu çözebileceğimi buldum:

dot_velocity = ball_1.velocity.dot(ball_2.velocity);
mtd_factor = 1. + 0.5 * Math.abs(dot_velocity * Math.sin(collision_angle));
mtd.multplyScalar(mtd_factor);

Bu düzeltmeden önce ve sonra toplam kinetik enerjinin her çarpışma için korunduğunu doğruladım. Mtd_factor'daki 0.5 değeri, bir çarpışmadan sonra topların her zaman ayrılmasına neden olduğu bulunan yaklaşık minumum değerdi.

Bu düzeltme, sistemin tam fiziğinde küçük bir hata hatası oluşturmasına rağmen, artık çok hızlı topların zaman adımı boyutunu küçültmeden bir tarayıcıda simüle edilebilmesidir.


1
sin (..) ucuz bir fonksiyon değil
PaulHK
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.