Lingkaran di dalam lingkaran tabrakan

9

Di salah satu proyek saya, saya memiliki area permainan dalam bentuk lingkaran. Di dalam lingkaran ini, lingkaran kecil lainnya bergerak. Yang ingin saya lakukan adalah menjaga lingkaran kecil agar tidak bergerak di luar lingkaran yang lebih besar. Di bawah ini Anda dapat melihat bahwa dalam bingkai 2 lingkaran kecil sebagian di luar, saya perlu cara untuk memindahkannya kembali tepat sebelum akan pindah ke luar. Bagaimana ini bisa dilakukan?

Contoh dasar

Juga, saya membutuhkan titik tumbukan di sepanjang busur lingkaran besar sehingga saya dapat memperbarui kecepatan lingkaran kecil itu. Bagaimana cara menghitung poin ini?

Apa yang ingin saya lakukan adalah sebelum memindahkan lingkaran kecil, saya memprediksi posisi berikutnya dan jika berada di luar saya menemukan waktu tabrakan antara t = 0 dan t = 1 (t = 1 langkah waktu penuh). Jika saya memiliki waktu t tabrakan maka saya hanya memindahkan lingkaran kecil selama t bukannya langkah waktu penuh. Tetapi sekali lagi, masalahnya adalah saya tidak tahu cara mendeteksi pada saat itu tabrakan terjadi ketika datang ke dua lingkaran dan satu berada di dalam yang lain.

EDIT:

Contoh titik tumbukan (hijau) yang ingin saya temukan. Mungkin gambarnya sedikit salah tetapi Anda mendapatkan idenya.

masukkan deskripsi gambar di sini

dbostream
sumber

Jawaban:

10

Mari kita asumsikan lingkaran besar memiliki pusat Adan jari R- jari dan lingkaran kecil memiliki pusat Bdan jari r- jari bergerak menuju lokasi C.

Ada cara yang elegan untuk menyelesaikan masalah ini, menggunakan jumlah Minkovski (pengurangan, sebenarnya): ganti cakram jari R- jari dengan cakram jari-jari R-r, dan cakram jari r- jari dengan cakram jari-jari 0, yaitu. titik sederhana yang terletak di B. Masalahnya menjadi masalah persimpangan garis-lingkaran.

Anda kemudian hanya perlu memeriksa apakah jarak AClebih kecil dari R-r. Jika ya, lingkaran tidak bertabrakan. Jika lebih besar, hanya menemukan titik Dpada BCpada jarak R-rdari Adan ini adalah lokasi baru pusat lingkaran kecil Anda. Ini sama dengan menemukan ksedemikian rupa sehingga:

  vec(BD) = k*vec(BC)
and
  norm(vec(AD)) = R-r

Mengganti vec(AD)dengan vec(AB) + vec(BD)memberi:

AB² + k² BC² + 2k vec(AB).vec(BC) = (R-r

Asalkan posisi awal berada di dalam lingkaran besar, persamaan kuadrat ini kmemiliki satu akar positif. Berikut ini cara menyelesaikan persamaan, dalam pseudocode:

b = - vec(AB).vec(BC) / BC²    // dot product
c = (AB² - (R-r)²) / BC²
d = b*b - c
k = b - sqrt(d)
if (k < 0)
    k = b + sqrt(d)
if (k < 0)
    // no solution! we must be slightly out of the large circle

Dengan nilai ini k, pusat baru lingkaran kecil adalah Dsedemikian rupa BD = kBC.

Sunting : tambahkan solusi persamaan kuadrat

sam hocevar
sumber
Terima kasih, memang terlihat elegan tapi saya tidak yakin saya mengerti. Misalnya: "temukan saja titik D pada BC pada jarak Rr dari A". Saya membuat gambar untuk mencoba memahami dengan lebih baik. Jadi jika kita mulai dari B (AX, AY- (Rr)) dan C adalah tempat kita akan berakhir dengan kecepatan saat ini. Cara saya memahami teks yang dikutip: Temukan titik D pada segmen garis BC yang merupakan jarak Rr dari A. Tetapi cara saya melihatnya pada gambar yang saya gambar adalah bahwa hanya B yang tepat adalah Rr yang jauh dari A. Semua poin lainnya adalah> Rr jauhnya dari A. Apa yang saya lewatkan?
dbostream
@dbostream Anda tidak melewatkan apa pun. Jika kedua lingkaran sudah terhubung maka tidak ada tabrakan nyata untuk dideteksi : tabrakan terjadi B, dan k=0. Sekarang jika resolusi tabrakan yang Anda inginkan, saya belum membahasnya dalam jawaban saya karena akan membutuhkan pengetahuan tentang sifat fisik objek. Apa yang seharusnya terjadi? Haruskah lingkaran dalam memantul di dalam? Atau berguling? Menyapu?
sam hocevar
Saya ingin lingkaran kecil mulai meluncur di sepanjang lengkungan lingkaran besar. Jadi jika saya tidak salah, saya ingin titik tumbukan pada busur lingkaran besar sehingga saya dapat menggunakan normal untuk memperbarui kecepatan.
dbostream
@dbostream jika gerakan harus dibatasi sedemikian rupa maka saya sarankan Anda mengikuti kendala itu sesegera mungkin: jika kecepatannya V, buat lingkaran dalam bergerak di V*tsepanjang keliling R-rlingkaran. Ini berarti rotasi sudut V*t/(R-r)radian di sekitar titik A. Dan vektor kecepatan dapat diputar dengan cara yang sama. Tidak perlu tahu yang normal (yang selalu berorientasi pada pusat lingkaran) atau untuk memperbarui kecepatan dengan cara lain.
sam hocevar
Saya masih perlu memindahkan lingkaran kecil ke titik tabrakan sebelum berputar. Dan ketika saya mencoba memutar posisi menggunakan matriks rotasi posisi baru tidak persis (tapi hampir) Rr menjauh dari pusat lingkaran besar, tetapi perbedaan kecil itu cukup untuk membuat uji tabrakan saya gagal pada putaran berikutnya. Apakah saya harus memutar posisi untuk menemukan yang baru, apakah tidak mungkin menggunakan operasi vektor seperti yang Anda bisa jika sesuatu bertabrakan dengan dinding lurus?
dbostream
4

Katakanlah lingkaran besar itu lingkaran A dan lingkaran kecil itu lingkaran B.

Periksa untuk melihat apakah B ada di dalam A:

distance = sqrt((B.x - A.x)^2 + (B.y - A.y)^2))
if(distance > A.Radius + B.Radius) { // B is outside A }

Jika dalam frame n-1B berada di dalam A dan dalam frame nB di luar A dan waktu antara frame tidak terlalu besar (alias B tidak bergerak terlalu cepat) kita dapat memperkirakan titik tabrakan dengan hanya menemukan koordinat Cartesian dari B relatif ke A:

collision.X = B.X - A.X;
collision.Y = B.Y - A.Y;

Kami kemudian dapat mengonversi poin ini ke sudut:

collision.Normalize(); //not 100% certain if this step is necessary     
radians = atan2(collision.Y, collision.X)

Jika Anda ingin tahu lebih tepat pada apa tB di luar A untuk pertama kalinya Anda bisa melakukan persimpangan lingkaran-ray setiap frame dan kemudian membandingkan jika jarak dari B ke titik tabrakan lebih besar maka jarak B dapat melakukan perjalanan mengingat itu kecepatan saat ini. Jika demikian, Anda dapat menghitung waktu tumbukan yang tepat.

Roy T.
sumber
Terima kasih, tetapi apakah benar menembakkan sinar dari pusat lingkaran kecil ketika melakukan tes persimpangan? Bukankah kita akan berakhir dengan skenario di tengah-tengah gambar ini ? Maksud saya titik pertama pada busur lingkaran kecil untuk bertabrakan dengan lingkaran besar belum tentu yang pada busur ke arah kecepatan. Saya pikir saya perlu sesuatu seperti di skenario bawah gambar yang saya tautkan. Saya telah menambahkan gambar baru di posting pertama yang menunjukkan contoh apa yang saya pikir saya butuhkan.
dbostream
Hmm saya kira skenario itu mungkin. Mungkin tes dengan lingkaran C baru yang memiliki B.Radius + gerakan maksimum B bingkai ini, periksa apakah itu bertabrakan dengan A dan kemudian latih titik pada C yang lebih jauh dari A. (Belum mencoba ini btw)
Roy T.
3
Menggunakan lebih sedikit kata: if (distance (A, B))> (Ra-Rb) terjadi tabrakan dan Anda hanya memindahkan lingkaran kecil untuk mendapatkan jarak yang sama dengan Ra-Rb. Jika tidak, Anda memindahkan lingkaran kecil secara normal. Ngomong-ngomong @dbostream Anda menggunakan sesuatu yang mirip dengan bentuk Kontak Spekulatif yang disederhanakan, coba cari itu.
Darkwings
@Darkwings +1 Anda benar sekali, dan itu membuatnya terdengar lebih sederhana!
Roy T.
Kedengarannya sederhana karena saya menanggalkan semua geometri dasar yang dibutuhkan. Alih-alih menamakannya 'tabrakan' Anda bisa menamainya terikat karena itu yang sebenarnya: vektor bebas AB terikat ke (0,0). Setelah Anda menormalkannya, Anda akan mendapatkan persamaan untuk bundel AB ke garis lurus dan juga vektor satuan yang berguna. Kemudian Anda dapat mengalikan vektor satuan tersebut untuk jarak D apa pun dan menambahkan paramen yang baru ditemukan ke A dengan menemukan titik tumbukan yang Anda butuhkan: C (Ax + Dx, Ay + Dy). Sekarang kedengarannya lebih rumit, tetapi itu adalah hal yang sama: P
Darkwings
0

Biarkan (Xa, Ya) posisi lingkaran besar dan jari-jari R, dan (Xb, Yb) posisi lingkaran kecil dan jari-jari r.

Anda dapat memeriksa apakah kedua lingkaran ini bertabrakan jika

DistanceTest = sqrt(((Xa - Xb) ^ 2) + ((Ya - Yb) ^ 2)) >= (R - r)

Untuk mengetahui posisi tabrakan, temukan momen waktu yang tepat ketika lingkaran bertabrakan, dengan menggunakan pencarian biner tetapi dengan sejumlah langkah tetap. Bergantung pada bagaimana permainan Anda dibuat, Anda dapat mengoptimalkan bagian kode ini (saya memberikan solusi ini untuk tidak tergantung pada bagaimana bola kecil berperilaku. Jika memiliki akselerasi konstan, atau kecepatan konstan, bagian kode ini dapat dioptimalkan dan diganti dengan formula sederhana).

left = 0 //the frame with no collision
right = 1 //the frame after collision
steps = 8 //or some other value, depending on how accurate you want it to be
while (steps > 0)
    checktime = left + (right - left) / 2
    if DistanceTest(checktime) is inside circle //if at the moment in the middle of the interval [left;right] the small circle is still inside the bigger one
        then left = checktime //the collision is after this moment of time
        else right = checktime //the collision is before
    steps -= 1
finaltime = left + (right - left) / 2 // the moment of time will be somewhere in between, so we take the moment in the middle of interval to have a small overall error

Setelah Anda mengetahui waktu tumbukan, hitung posisi kedua lingkaran pada waktu terakhir dan titik tumbukan akhir adalah

CollisionX = (Xb - Xa)*R/(R-r) + Xa
CollisionY = (Yb - Ya)*R/(R-r) + Ya
vlad
sumber
0

Saya telah mengimplementasikan demo bola memantul di jsfiddle menggunakan algoritma yang dijelaskan oleh Sam Hocevar :

http://jsfiddle.net/klenwell/3ZdXf/

Inilah javascript yang mengidentifikasi titik kontak:

find_contact_point: function(world, ball) {
    // see https://gamedev.stackexchange.com/a/29658
    var A = world.point();
    var B = ball.point().subtract(ball.velocity());
    var C = ball.point();
    var R = world.r;
    var r = ball.r;

    var AB = B.subtract(A);
    var BC = C.subtract(B);
    var AB_len = AB.get_length();
    var BC_len = BC.get_length();

    var b = AB.dot(BC) / Math.pow(BC_len, 2) * -1;
    var c = (Math.pow(AB_len, 2) - Math.pow(R - r, 2)) / Math.pow(BC_len, 2);
    var d = b * b - c;
    var k = b - Math.sqrt(d);

    if ( k < 0 ) {
        k = b + Math.sqrt(d);
    }

    var BD = C.subtract(B);
    var BD_len = BC_len * k;
    BD.set_length(BD_len);

    var D = B.add(BD);
    return D;
}
Klenwell
sumber