Deteksi tabrakan Circle-Rectangle (persimpangan)

192

Bagaimana saya bisa tahu apakah lingkaran dan persegi panjang berpotongan dalam ruang Euclidean 2D? (yaitu geometri 2D klasik)

aib
sumber
1
Apakah persegi panjang selalu sejajar dengan sumbu, atau dapatkah diputar dengan sudut yang sewenang-wenang?
e.James
11
@ Eames: apa bedanya? Anda sedang memeriksa kotak untuk persimpangan dengan lingkaran ; Anda selalu dapat mengubah sistem koordinat Anda sehingga persegi panjang adalah sumbu-paralel tanpa perubahan dalam lingkaran :-)
ShreevatsaR
Anda harus menambahkan itu sebagai jawaban, berputar melalui -Θ dan semua ...
aib
2
@ShreevatsaR: Itu penting dalam hal apakah atau tidak saya perlu khawatir tentang terjemahan koordinat atau tidak. @aib: Ya ampun!
e.James

Jawaban:

191

Hanya ada dua kasus ketika lingkaran bersinggungan dengan persegi panjang:

  • Entah pusat lingkaran terletak di dalam persegi panjang, atau
  • Salah satu tepi persegi panjang memiliki titik di lingkaran.

Perhatikan bahwa ini tidak memerlukan persegi panjang untuk menjadi sumbu-paralel.

Beberapa cara yang berbeda, sebuah lingkaran dan persegi panjang dapat bersilangan

(Salah satu cara untuk melihat ini: jika tidak ada sisi yang memiliki titik di dalam lingkaran (jika semua ujungnya benar-benar "di luar" lingkaran), maka satu-satunya cara lingkaran masih dapat memotong poligon adalah jika itu terletak di dalam poligon.)

Dengan wawasan itu, sesuatu seperti berikut ini akan bekerja, di mana lingkaran memiliki pusat Pdan jari-jari R, dan persegi panjang memiliki simpul A, B, C,D agar (kode tidak lengkap):

def intersect(Circle(P, R), Rectangle(A, B, C, D)):
    S = Circle(P, R)
    return (pointInRectangle(P, Rectangle(A, B, C, D)) or
            intersectCircle(S, (A, B)) or
            intersectCircle(S, (B, C)) or
            intersectCircle(S, (C, D)) or
            intersectCircle(S, (D, A)))

Jika Anda sedang menulis geometri apa pun, Anda mungkin sudah memiliki fungsi-fungsi di atas di perpustakaan Anda. Kalau tidak, pointInRectangle()dapat diimplementasikan dalam beberapa cara; salah satu titik umum dalam poligon metode akan bekerja, tetapi untuk persegi panjang Anda bisa memeriksa apakah ini bekerja:

0 ≤ AP·AB ≤ AB·AB and 0 ≤ AP·AD ≤ AD·AD

Dan intersectCircle() juga mudah diterapkan: salah satu caranya adalah dengan memeriksa apakah kaki tegak lurus dari Pke garis cukup dekat dan di antara titik-titik akhir, dan periksa titik-titik ujung sebaliknya.

Yang keren adalah bahwa ide yang sama bekerja tidak hanya untuk persegi panjang tetapi untuk persimpangan lingkaran dengan poligon sederhana - bahkan tidak harus cembung!

ShreevatsaR
sumber
25
Untuk apa nilainya, saya pikir jawaban ini lebih baik daripada jawaban saya. Dua alasan utama: 1: tidak memerlukan rotasi jika persegi panjangnya tidak sejajar sumbu, dan, 2: konsepnya dengan mudah meluas ke semua poligon.
e.James
2
@paniq: Ya, keduanya adalah waktu yang konstan. :-) Tapi ya, ini lebih berguna sebagai solusi umum, mencakup persegi panjang dengan orientasi apa pun, dan sebenarnya poligon sederhana.
ShreevatsaR
7
bagaimana dengan kasus di mana persegi panjang benar-benar di dalam lingkaran, tetapi pusat lingkaran tidak di dalam persegi panjang?
ericsoco
2
@ ericsoco: Pengamatan yang bagus. :-) Saya kira saya seharusnya mengatakan "memotong cakram" di "salah satu tepi persegi panjang memotong lingkaran", karena yang saya maksudkan adalah bahwa ia berbagi titik dengan lingkaran itu sendiri, belum tentu batas lingkaran. Bagaimanapun, uraian di atas, "periksa apakah kaki tegak lurus dari P [pusat lingkaran] ke garis cukup dekat dan di antara titik-titik akhir, dan periksa titik-titik akhir jika tidak," masih akan berfungsi - misalnya titik akhir terletak di dalam lingkaran ( cakram).
ShreevatsaR
2
@ DexD.Hunter Jika pusat lingkaran berada di luar persegi panjang, tetapi sebagian darinya berada di dalam persegi panjang, maka salah satu ujung persegi panjang akan memotong lingkaran.
ShreevatsaR
289

Inilah cara saya akan melakukannya:

bool intersects(CircleType circle, RectType rect)
{
    circleDistance.x = abs(circle.x - rect.x);
    circleDistance.y = abs(circle.y - rect.y);

    if (circleDistance.x > (rect.width/2 + circle.r)) { return false; }
    if (circleDistance.y > (rect.height/2 + circle.r)) { return false; }

    if (circleDistance.x <= (rect.width/2)) { return true; } 
    if (circleDistance.y <= (rect.height/2)) { return true; }

    cornerDistance_sq = (circleDistance.x - rect.width/2)^2 +
                         (circleDistance.y - rect.height/2)^2;

    return (cornerDistance_sq <= (circle.r^2));
}

Begini cara kerjanya:

ilusi

  1. Pasangan garis pertama menghitung nilai absolut dari perbedaan x dan y antara pusat lingkaran dan pusat persegi panjang. Ini meruntuhkan keempat kuadran menjadi satu, sehingga perhitungan tidak harus dilakukan empat kali. Gambar menunjukkan area di mana pusat lingkaran sekarang harus berada. Perhatikan bahwa hanya kuadran tunggal yang ditampilkan. Persegi panjang adalah area abu-abu, dan batas merah menguraikan area kritis yang persis satu radius dari tepi persegi panjang. Pusat lingkaran harus berada dalam batas merah ini agar persimpangan dapat terjadi.

  2. Pasangan kedua garis menghilangkan kasus mudah di mana lingkaran cukup jauh dari persegi panjang (di kedua arah) sehingga tidak ada persimpangan yang mungkin. Ini sesuai dengan area hijau pada gambar.

  3. Pasangan garis ketiga menangani kotak yang mudah di mana lingkaran cukup dekat dengan persegi panjang (di kedua arah) yang dijamin persimpangan. Ini sesuai dengan bagian oranye dan abu-abu pada gambar. Perhatikan bahwa langkah ini harus dilakukan setelah langkah 2 agar logika masuk akal.

  4. Baris yang tersisa menghitung case yang sulit di mana lingkaran dapat memotong sudut persegi panjang. Untuk menyelesaikan, hitung jarak dari pusat lingkaran dan sudut, lalu verifikasi bahwa jaraknya tidak lebih dari jari-jari lingkaran. Perhitungan ini menghasilkan false untuk semua lingkaran yang pusatnya ada di dalam area berbayang merah dan mengembalikan true untuk semua lingkaran yang pusatnya ada di dalam area berbayang putih.

e.James
sumber
4
Sangat bagus! Catatan: ternyata di sini, rect.x / y berada di sudut kanan atas persegi panjang. Anda juga dapat menghilangkan akar kuadrat yang mahal, dengan membandingkannya dengan kuadrat jari-jari.
luqui
2
Oh tidak, salahku. rect.x / y berada di kiri bawah persegi panjang. Saya akan menulis: circleDistance.x = abs (circle.x - (rect.x + rect.width / 2));
luqui
2
@ Tanner: Ini dia. Hore untuk backup dan OCD;)
e.James
11
hanya untuk memperjelas - jawaban ini hanya berlaku untuk persegi panjang yang selaras sumbu. itu jelas dari membaca komentar pada jawaban lain tetapi tidak jelas dari jawaban ini + komentar saja. (jawaban yang bagus untuk rect yang disejajarkan dengan sumbu!)
ericsoco
3
Bagus! Sangat penting bagi pembaca untuk mengetahui bahwa di sini saya percaya definisi dari sebuah rect adalah rect.x & rect.y adalah pusat dari rect. Di duniaku xy kotak adalah bagian atas / kiri dari kotak, dan 0,0 adalah bagian atas / kiri layar, jadi saya menggunakan:circleDistance_x = abs(circle.x - (rect.x-rect.w/2)); circleDistance_y = abs(circle.y - (rect.y-rect.h/2));
erco
123

Berikut adalah solusi lain yang cukup sederhana untuk diimplementasikan (dan juga cukup cepat). Ini akan menangkap semua persimpangan, termasuk ketika bola telah sepenuhnya memasuki persegi panjang.

// clamp(value, min, max) - limits value to the range min..max

// Find the closest point to the circle within the rectangle
float closestX = clamp(circle.X, rectangle.Left, rectangle.Right);
float closestY = clamp(circle.Y, rectangle.Top, rectangle.Bottom);

// Calculate the distance between the circle's center and this closest point
float distanceX = circle.X - closestX;
float distanceY = circle.Y - closestY;

// If the distance is less than the circle's radius, an intersection occurs
float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
return distanceSquared < (circle.Radius * circle.Radius);

Dengan perpustakaan matematika yang layak, itu dapat disingkat menjadi 3 atau 4 baris.

Cygon
sumber
3
Anda memiliki bug di sana, Anda mencari terdekatY dengan Kiri dan Kanan, bukan Atas dan Bawah, solusi yang indah.
manveru
8
Saya suka jawaban ini yang terbaik. Ini pendek, mudah dimengerti, dan cepat.
John Kurlak
2
Saya pikir solusi Anda gagal jika persegi panjang miring ke sumbu x dan y.
Leo
3
@ Leo Saya pikir tidak sulit untuk memodifikasi algoritma ini untuk mengakomodasi kasus itu, orang hanya harus menerapkan transformasi koordinat di mana asal berada di pusat persegi panjang dan persegi panjang tidak miring lagi. Anda perlu menerapkan transformasi ke pusat lingkaran saja.
enobayram
1
Ini pada dasarnya sama dengan kode yang ditemukan di migapro.com/circle-and-rotated-rectangle-collision-detection yang saya juga porting ke Objective-C. Bekerja sangat baik; itu solusi yang bagus untuk masalah ini.
PKCLsoft
10

bola Anda dan rectect berpotongan IIF
jarak antara pusat lingkaran dan satu simpul persegi Anda lebih kecil dari jari-jari bola Anda
ATAU
jarak antara pusat lingkaran dan satu ujung persegi Anda lebih kecil dari jari-jari bola Anda ( [ jarak titik-garis ])
ATAU
pusat lingkaran berada di dalam

jarak titik-titik persegi:

P1 = [x1, y1]
P2 = [x2, y2]
Jarak = sqrt (abs (x1 - x2) + abs (y1-y2))

jarak titik-garis:

L1 = [x1, y1], L2 = [x2, y2] (dua titik dari garis Anda, yaitu titik simpul)
P1 = [px, py] suatu titik

Jarak d = abs ((x2-x1) (y1-py) - (x1-px) (y2-y1)) / Jarak (L1, L2)


lingkaran pusat di
dalam persegi : ambil pendekatan sumbu pemisah: jika ada proyeksi ke garis yang memisahkan persegi panjang dari titik, mereka tidak berpotongan

Anda memproyeksikan titik pada garis sejajar dengan sisi persegi Anda dan kemudian dapat dengan mudah menentukan apakah mereka berpotongan. jika mereka berpotongan tidak pada semua 4 proyeksi, mereka (titik dan persegi panjang) tidak dapat berpotongan.

Anda hanya perlu produk dalam (x = [x1, x2], y = [y1, y2], x * y = x1 * y1 + x2 * y2)

tes Anda akan terlihat seperti itu:

// tepi persegi panjang: TL (kiri atas), TR (kanan atas), BL (kiri bawah), BR (kanan bawah)
// arahkan ke tes: POI

dipisahkan = salah
untuk egde dalam {{TL, TR}, {BL, BR}, {TL, BL}, {TR-BR}}: // the edge
    D = tepi [0] - tepi [1]
    innerProd = D * POI
    Interval_min = min (D * edge [0], D * edge [1])
    Interval_max = maks (D * tepi [0], D * tepi [1])
    jika tidak (Interval_min ≤ innerProd ≤ Interval_max) 
           dipisahkan = benar
           break // end for loop 
    berakhir jika
berakhir untuk
jika (dipisahkan benar)    
      kembali "tidak ada persimpangan"
lain 
      kembali "persimpangan"
berakhir jika

ini tidak mengasumsikan persegi panjang sejajar sumbu dan mudah diperpanjang untuk menguji persimpangan antara set cembung.

pengguna104676
sumber
1
Bukankah jarak titik ke titik menggunakan kotak, bukan abs?
Thomas
6

Ini adalah solusi tercepat:

public static boolean intersect(Rectangle r, Circle c)
{
    float cx = Math.abs(c.x - r.x - r.halfWidth);
    float xDist = r.halfWidth + c.radius;
    if (cx > xDist)
        return false;
    float cy = Math.abs(c.y - r.y - r.halfHeight);
    float yDist = r.halfHeight + c.radius;
    if (cy > yDist)
        return false;
    if (cx <= r.halfWidth || cy <= r.halfHeight)
        return true;
    float xCornerDist = cx - r.halfWidth;
    float yCornerDist = cy - r.halfHeight;
    float xCornerDistSq = xCornerDist * xCornerDist;
    float yCornerDistSq = yCornerDist * yCornerDist;
    float maxCornerDistSq = c.radius * c.radius;
    return xCornerDistSq + yCornerDistSq <= maxCornerDistSq;
}

Perhatikan urutan eksekusi, dan setengah lebar / tinggi sudah dihitung sebelumnya. Juga squaring dilakukan "secara manual" untuk menghemat beberapa siklus jam.

intrepidis
sumber
3
Saya tidak berpikir Anda dapat mengklaim bahwa lima tes / perbandingan di jalur kode paling mahal adalah "solusi tercepat" tanpa bukti.
sam hocevar
1
Dalam pengalaman saya dengan metode ini, tabrakan tidak sering terjadi. Oleh karena itu tes akan menyebabkan keluar sebelum sebelum sebagian besar kode dieksekusi.
intrepidis
6

Solusi paling sederhana yang saya buat adalah cukup mudah.

Ia bekerja dengan menemukan titik dalam persegi panjang yang paling dekat dengan lingkaran, lalu membandingkan jarak.

Anda dapat melakukan semua ini dengan beberapa operasi, dan bahkan menghindari fungsi sqrt.

public boolean intersects(float cx, float cy, float radius, float left, float top, float right, float bottom)
{
   float closestX = (cx < left ? left : (cx > right ? right : cx));
   float closestY = (cy < top ? top : (cy > bottom ? bottom : cy));
   float dx = closestX - cx;
   float dy = closestY - cy;

   return ( dx * dx + dy * dy ) <= radius * radius;
}

Dan itu dia! Solusi di atas mengasumsikan asal di kiri atas dunia dengan sumbu x mengarah ke bawah.

Jika Anda menginginkan solusi untuk menangani tabrakan antara lingkaran bergerak dan persegi panjang, itu jauh lebih rumit dan tercakup dalam jawaban saya yang lain.

ClickerMonkey
sumber
Ini akan gagal mendeteksi persimpangan jika jari-jari lingkaran terlalu kecil dan pusatnya ada di dalam persegi panjang!
Yoav
2
Bisakah Anda memberikan input aktual yang membuat ini gagal? Ketika lingkaran di dalam, bagian kiri tes adalah 0,0. Kecuali jika jari-jarinya nol, bagian kanan tes harus> 0,0
ClickerMonkey
Apakah ini akan bekerja untuk persegi panjang yang diputar juga? jika tidak maka tolong beri saya petunjuk tentang itu .....
M Abdul Sami
4

Sebenarnya, ini jauh lebih sederhana. Anda hanya membutuhkan dua hal.

Pertama, Anda perlu menemukan empat jarak ortogonal dari pusat lingkaran ke setiap garis persegi panjang. Maka lingkaran Anda tidak akan memotong persegi panjang jika ketiganya lebih besar dari jari-jari lingkaran.

Kedua, Anda perlu menemukan jarak antara pusat lingkaran dan pusat persegi panjang, maka Anda lingkaran tidak akan berada di dalam persegi panjang jika jaraknya lebih besar dari setengah panjang diagonal persegi panjang.

Semoga berhasil!


sumber
3

Inilah kode C saya untuk menyelesaikan tabrakan antara bola dan kotak yang tidak disejajarkan. Itu bergantung pada beberapa rutinitas perpustakaan saya sendiri, tetapi mungkin terbukti bermanfaat bagi sebagian orang. Saya menggunakannya dalam sebuah game dan bekerja dengan sempurna.

float physicsProcessCollisionBetweenSelfAndActorRect(SPhysics *self, SPhysics *actor)
{
    float diff = 99999;

    SVector relative_position_of_circle = getDifference2DBetweenVectors(&self->worldPosition, &actor->worldPosition);
    rotateVector2DBy(&relative_position_of_circle, -actor->axis.angleZ); // This aligns the coord system so the rect becomes an AABB

    float x_clamped_within_rectangle = relative_position_of_circle.x;
    float y_clamped_within_rectangle = relative_position_of_circle.y;
    LIMIT(x_clamped_within_rectangle, actor->physicsRect.l, actor->physicsRect.r);
    LIMIT(y_clamped_within_rectangle, actor->physicsRect.b, actor->physicsRect.t);

    // Calculate the distance between the circle's center and this closest point
    float distance_to_nearest_edge_x = relative_position_of_circle.x - x_clamped_within_rectangle;
    float distance_to_nearest_edge_y = relative_position_of_circle.y - y_clamped_within_rectangle;

    // If the distance is less than the circle's radius, an intersection occurs
    float distance_sq_x = SQUARE(distance_to_nearest_edge_x);
    float distance_sq_y = SQUARE(distance_to_nearest_edge_y);
    float radius_sq = SQUARE(self->physicsRadius);
    if(distance_sq_x + distance_sq_y < radius_sq)   
    {
        float half_rect_w = (actor->physicsRect.r - actor->physicsRect.l) * 0.5f;
        float half_rect_h = (actor->physicsRect.t - actor->physicsRect.b) * 0.5f;

        CREATE_VECTOR(push_vector);         

        // If we're at one of the corners of this object, treat this as a circular/circular collision
        if(fabs(relative_position_of_circle.x) > half_rect_w && fabs(relative_position_of_circle.y) > half_rect_h)
        {
            SVector edges;
            if(relative_position_of_circle.x > 0) edges.x = half_rect_w; else edges.x = -half_rect_w;
            if(relative_position_of_circle.y > 0) edges.y = half_rect_h; else edges.y = -half_rect_h;   

            push_vector = relative_position_of_circle;
            moveVectorByInverseVector2D(&push_vector, &edges);

            // We now have the vector from the corner of the rect to the point.
            float delta_length = getVector2DMagnitude(&push_vector);
            float diff = self->physicsRadius - delta_length; // Find out how far away we are from our ideal distance

            // Normalise the vector
            push_vector.x /= delta_length;
            push_vector.y /= delta_length;
            scaleVector2DBy(&push_vector, diff); // Now multiply it by the difference
            push_vector.z = 0;
        }
        else // Nope - just bouncing against one of the edges
        {
            if(relative_position_of_circle.x > 0) // Ball is to the right
                push_vector.x = (half_rect_w + self->physicsRadius) - relative_position_of_circle.x;
            else
                push_vector.x = -((half_rect_w + self->physicsRadius) + relative_position_of_circle.x);

            if(relative_position_of_circle.y > 0) // Ball is above
                push_vector.y = (half_rect_h + self->physicsRadius) - relative_position_of_circle.y;
            else
                push_vector.y = -((half_rect_h + self->physicsRadius) + relative_position_of_circle.y);

            if(fabs(push_vector.x) < fabs(push_vector.y))
                push_vector.y = 0;
            else
                push_vector.x = 0;
        }

        diff = 0; // Cheat, since we don't do anything with the value anyway
        rotateVector2DBy(&push_vector, actor->axis.angleZ);
        SVector *from = &self->worldPosition;       
        moveVectorBy2D(from, push_vector.x, push_vector.y);
    }   
    return diff;
}
Madrayken
sumber
2

Untuk memvisualisasikan, ambil numpad keyboard Anda. Jika kunci '5' mewakili persegi panjang Anda, maka semua kunci 1-9 mewakili 9 kuadran ruang dibagi dengan garis-garis yang membentuk persegi panjang Anda (dengan 5 menjadi bagian dalam.)

1) Jika pusat lingkaran berada di kuadran 5 (yaitu di dalam persegi panjang) maka kedua bentuk berpotongan.

Dengan keluar dari jalan, ada dua kasus yang mungkin: a) Lingkaran berpotongan dengan dua atau lebih tepi berdekatan dari persegi panjang. b) Lingkaran bersinggungan dengan satu ujung persegi panjang.

Kasus pertama sederhana. Jika lingkaran berpotongan dengan dua sisi persegi yang berdekatan, itu harus berisi sudut yang menghubungkan kedua tepi. (Itu, atau pusatnya terletak pada kuadran 5, yang telah kita bahas. Juga perhatikan bahwa kasus di mana lingkaran bersilangan dengan hanya dua sisi yang berlawanan dari persegi panjang juga tercakup.)

2) Jika salah satu sudut A, B, C, D dari persegi panjang terletak di dalam lingkaran, maka kedua bentuk berpotongan.

Kasus kedua lebih rumit. Kita harus mencatat bahwa itu hanya dapat terjadi ketika pusat lingkaran terletak di salah satu kuadran 2, 4, 6 atau 8. (Faktanya, jika pusatnya berada di salah satu kuadran 1, 3, 7, 8, sudut yang sesuai akan menjadi titik terdekat dengan itu.)

Sekarang kita memiliki kasus bahwa pusat lingkaran berada di salah satu kuadran 'tepi', dan hanya memotong dengan tepi yang sesuai. Kemudian, titik di tepi yang paling dekat dengan pusat lingkaran, harus berada di dalam lingkaran.

3) Untuk setiap garis AB, BC, CD, DA, buat garis tegak lurus p (AB, P), p (BC, P), p (CD, P), p (DA, P) melalui pusat lingkaran P. Untuk setiap garis tegak lurus, jika persimpangan dengan tepi asli terletak di dalam lingkaran, maka kedua bentuk tersebut bersilangan.

Ada jalan pintas untuk langkah terakhir ini. Jika pusat lingkaran berada di kuadran 8 dan tepi AB adalah tepi atas, titik persimpangan akan memiliki koordinat y dari A dan B, dan koordinat x dari pusat P.

Anda bisa membuat empat persimpangan garis dan memeriksa apakah mereka terletak di tepi yang sesuai, atau mencari tahu di kuadran P mana dan memeriksa persimpangan yang sesuai. Keduanya harus menyederhanakan persamaan boolean yang sama. Berhati-hatilah karena langkah 2 di atas tidak mengesampingkan P berada di salah satu kuadran 'sudut'; itu hanya mencari persimpangan.

Sunting: Ternyata, saya telah mengabaikan fakta sederhana bahwa # 2 adalah subkotak dari # 3 di atas. Lagi pula, sudut juga merupakan titik di tepinya. Lihat jawaban @ ShreevatsaR di bawah ini untuk penjelasan yang bagus. Dan sementara itu, lupakan # 2 di atas kecuali Anda ingin cek cepat tapi berlebihan.

aib
sumber
2

Fungsi ini mendeteksi tabrakan (persimpangan) antara Circle dan Rectangle. Dia bekerja seperti metode e.James dalam jawabannya, tetapi yang ini mendeteksi tabrakan untuk semua sudut persegi panjang (tidak hanya sudut kanan atas).

CATATAN:

aRect.origin.x dan aRect.origin.y adalah koordinat sudut kiri bawah persegi panjang!

aCircle.x dan aCircle.y adalah koordinat dari Circle Center!

static inline BOOL RectIntersectsCircle(CGRect aRect, Circle aCircle) {

    float testX = aCircle.x;
    float testY = aCircle.y;

    if (testX < aRect.origin.x)
        testX = aRect.origin.x;
    if (testX > (aRect.origin.x + aRect.size.width))
        testX = (aRect.origin.x + aRect.size.width);
    if (testY < aRect.origin.y)
        testY = aRect.origin.y;
    if (testY > (aRect.origin.y + aRect.size.height))
        testY = (aRect.origin.y + aRect.size.height);

    return ((aCircle.x - testX) * (aCircle.x - testX) + (aCircle.y - testY) * (aCircle.y - testY)) < aCircle.radius * aCircle.radius;
}
Faraona
sumber
1

Saya punya metode yang menghindari pythagoras yang mahal jika tidak perlu - yaitu. ketika kotak kotak persegi panjang dan lingkaran tidak berpotongan.

Dan itu akan bekerja untuk non-euclidean juga:

class Circle {
 // create the bounding box of the circle only once
 BBox bbox;

 public boolean intersect(BBox b) {
    // test top intersect
    if (lat > b.maxLat) {
        if (lon < b.minLon)
            return normDist(b.maxLat, b.minLon) <= normedDist;
        if (lon > b.maxLon)
            return normDist(b.maxLat, b.maxLon) <= normedDist;
        return b.maxLat - bbox.minLat > 0;
    }

    // test bottom intersect
    if (lat < b.minLat) {
        if (lon < b.minLon)
            return normDist(b.minLat, b.minLon) <= normedDist;
        if (lon > b.maxLon)
            return normDist(b.minLat, b.maxLon) <= normedDist;
        return bbox.maxLat - b.minLat > 0;
    }

    // test middle intersect
    if (lon < b.minLon)
        return bbox.maxLon - b.minLon > 0;
    if (lon > b.maxLon)
        return b.maxLon - bbox.minLon > 0;
    return true;
  }
}
  • minLat, maxLat dapat diganti dengan minY, maxY dan sama untuk minLon, maxLon: ganti dengan minX, maxX
  • normDist adalah metode yang sedikit lebih cepat daripada perhitungan jarak penuh. Misalnya tanpa akar kuadrat dalam ruang euclidean (atau tanpa banyak hal-hal lain untuk haversine): dLat=(lat-circleY); dLon=(lon-circleX); normed=dLat*dLat+dLon*dLon. Tentu saja jika Anda menggunakan metode normDist Anda harus membuat buat normedDist = dist*dist;untuk lingkaran

Lihat kode BBox dan Lingkaran penuh dari proyek GraphHopper saya .

Karussell
sumber
1

Saya membuat kelas untuk bekerja dengan bentuk yang Anda harap dapat Anda nikmati

public class Geomethry {
  public static boolean intersectionCircleAndRectangle(int circleX, int circleY, int circleR, int rectangleX, int rectangleY, int rectangleWidth, int rectangleHeight){
    boolean result = false;

    float rectHalfWidth = rectangleWidth/2.0f;
    float rectHalfHeight = rectangleHeight/2.0f;

    float rectCenterX = rectangleX + rectHalfWidth;
    float rectCenterY = rectangleY + rectHalfHeight;

    float deltax = Math.abs(rectCenterX - circleX);
    float deltay = Math.abs(rectCenterY - circleY);

    float lengthHypotenuseSqure = deltax*deltax + deltay*deltay;

    do{
        // check that distance between the centerse is more than the distance between the circumcircle of rectangle and circle
        if(lengthHypotenuseSqure > ((rectHalfWidth+circleR)*(rectHalfWidth+circleR) + (rectHalfHeight+circleR)*(rectHalfHeight+circleR))){
            //System.out.println("distance between the centerse is more than the distance between the circumcircle of rectangle and circle");
            break;
        }

        // check that distance between the centerse is less than the distance between the inscribed circle
        float rectMinHalfSide = Math.min(rectHalfWidth, rectHalfHeight);
        if(lengthHypotenuseSqure < ((rectMinHalfSide+circleR)*(rectMinHalfSide+circleR))){
            //System.out.println("distance between the centerse is less than the distance between the inscribed circle");
            result=true;
            break;
        }

        // check that the squares relate to angles
        if((deltax > (rectHalfWidth+circleR)*0.9) && (deltay > (rectHalfHeight+circleR)*0.9)){
            //System.out.println("squares relate to angles");
            result=true;
        }
    }while(false);

    return result;
}

public static boolean intersectionRectangleAndRectangle(int rectangleX, int rectangleY, int rectangleWidth, int rectangleHeight, int rectangleX2, int rectangleY2, int rectangleWidth2, int rectangleHeight2){
    boolean result = false;

    float rectHalfWidth = rectangleWidth/2.0f;
    float rectHalfHeight = rectangleHeight/2.0f;
    float rectHalfWidth2 = rectangleWidth2/2.0f;
    float rectHalfHeight2 = rectangleHeight2/2.0f;

    float deltax = Math.abs((rectangleX + rectHalfWidth) - (rectangleX2 + rectHalfWidth2));
    float deltay = Math.abs((rectangleY + rectHalfHeight) - (rectangleY2 + rectHalfHeight2));

    float lengthHypotenuseSqure = deltax*deltax + deltay*deltay;

    do{
        // check that distance between the centerse is more than the distance between the circumcircle
        if(lengthHypotenuseSqure > ((rectHalfWidth+rectHalfWidth2)*(rectHalfWidth+rectHalfWidth2) + (rectHalfHeight+rectHalfHeight2)*(rectHalfHeight+rectHalfHeight2))){
            //System.out.println("distance between the centerse is more than the distance between the circumcircle");
            break;
        }

        // check that distance between the centerse is less than the distance between the inscribed circle
        float rectMinHalfSide = Math.min(rectHalfWidth, rectHalfHeight);
        float rectMinHalfSide2 = Math.min(rectHalfWidth2, rectHalfHeight2);
        if(lengthHypotenuseSqure < ((rectMinHalfSide+rectMinHalfSide2)*(rectMinHalfSide+rectMinHalfSide2))){
            //System.out.println("distance between the centerse is less than the distance between the inscribed circle");
            result=true;
            break;
        }

        // check that the squares relate to angles
        if((deltax > (rectHalfWidth+rectHalfWidth2)*0.9) && (deltay > (rectHalfHeight+rectHalfHeight2)*0.9)){
            //System.out.println("squares relate to angles");
            result=true;
        }
    }while(false);

    return result;
  } 
}
pwipo
sumber
1

Berikut adalah kode yang dimodifikasi 100% berfungsi:

public static bool IsIntersected(PointF circle, float radius, RectangleF rectangle)
{
    var rectangleCenter = new PointF((rectangle.X +  rectangle.Width / 2),
                                     (rectangle.Y + rectangle.Height / 2));

    var w = rectangle.Width  / 2;
    var h = rectangle.Height / 2;

    var dx = Math.Abs(circle.X - rectangleCenter.X);
    var dy = Math.Abs(circle.Y - rectangleCenter.Y);

    if (dx > (radius + w) || dy > (radius + h)) return false;

    var circleDistance = new PointF
                             {
                                 X = Math.Abs(circle.X - rectangle.X - w),
                                 Y = Math.Abs(circle.Y - rectangle.Y - h)
                             };

    if (circleDistance.X <= (w))
    {
        return true;
    }

    if (circleDistance.Y <= (h))
    {
        return true;
    }

    var cornerDistanceSq = Math.Pow(circleDistance.X - w, 2) + 
                                    Math.Pow(circleDistance.Y - h, 2);

    return (cornerDistanceSq <= (Math.Pow(radius, 2)));
}

Bassam Alugili

Bassam Alugili
sumber
1

Inilah tes satu garis cepat untuk ini:

if (length(max(abs(center - rect_mid) - rect_halves, 0)) <= radius ) {
  // They intersect.
}

Ini adalah kasus selaras sumbu di mana rect_halvesvektor positif menunjuk dari persegi panjang ke sudut. Ekspresi di dalam length()adalah vektor delta dari centerke titik terdekat di kotak. Ini berfungsi dalam dimensi apa pun.

Tyler
sumber
1
  • Pertama periksa apakah persegi panjang dan garis singgung tangen ke lingkaran tumpang tindih (mudah). Jika mereka tidak tumpang tindih, mereka tidak bertabrakan.
  • Periksa apakah pusat lingkaran di dalam persegi panjang (mudah). Jika ada di dalam, mereka bertabrakan.
  • Hitung jarak kuadrat minimum dari sisi persegi panjang ke pusat lingkaran (agak keras). Jika lebih rendah dari jari-jari kuadrat, maka mereka bertabrakan, kalau tidak mereka tidak.

Ini efisien, karena:

  • Pertama, ia memeriksa skenario paling umum dengan algoritma murah dan ketika yakin mereka tidak bertabrakan, itu berakhir.
  • Kemudian ia memeriksa skenario paling umum berikutnya dengan algoritma murah (jangan menghitung root kuadrat, gunakan nilai kuadrat) dan ketika yakin mereka bertabrakan itu berakhir.
  • Kemudian ia mengeksekusi algoritma yang lebih mahal untuk memeriksa tabrakan dengan batas-batas persegi panjang.
David C.
sumber
1

bekerja untuk saya (hanya bekerja ketika sudut persegi panjang adalah 180)

function intersects(circle, rect) {
  let left = rect.x + rect.width > circle.x - circle.radius;
  let right = rect.x < circle.x + circle.radius;
  let top = rect.y < circle.y + circle.radius;
  let bottom = rect.y + rect.height > circle.y - circle.radius;
  return left && right && bottom && top;
}
sandeep
sumber
hmmm ... Saya memilih ini tetapi kemudian diuji dengan benar dan saya pikir itu tidak berfungsi di sudut misalnya. Ini akan bekerja untuk dua persegi panjang.
Dan Zen
1

Meningkatkan sedikit jawaban dari e.James:

double dx = abs(circle.x - rect.x) - rect.w / 2,
       dy = abs(circle.y - rect.y) - rect.h / 2;

if (dx > circle.r || dy > circle.r) { return false; }
if (dx <= 0 || dy <= 0) { return true; }

return (dx * dx + dy * dy <= circle.r * circle.r);

Ini mengurangi rect.w / 2dan rect.h / 2sekali bukannya tiga kali.

Estid Felipe Lozano Reyes
sumber
0

Bagi mereka yang harus menghitung tabrakan Circle / Rectangle di Geographic Coordinates dengan SQL,
ini adalah implementasi saya di oracle 11 dari algoritma yang disarankan e.James .

Dalam input itu membutuhkan koordinat lingkaran, jari-jari lingkaran dalam km dan dua koordinat titik persegi:

CREATE OR REPLACE FUNCTION "DETECT_CIRC_RECT_COLLISION"
(
    circleCenterLat     IN NUMBER,      -- circle Center Latitude
    circleCenterLon     IN NUMBER,      -- circle Center Longitude
    circleRadius        IN NUMBER,      -- circle Radius in KM
    rectSWLat           IN NUMBER,      -- rectangle South West Latitude
    rectSWLon           IN NUMBER,      -- rectangle South West Longitude
    rectNELat           IN NUMBER,      -- rectangle North Est Latitude
    rectNELon           IN NUMBER       -- rectangle North Est Longitude
)
RETURN NUMBER
AS
    -- converts km to degrees (use 69 if miles)
    kmToDegreeConst     NUMBER := 111.045;

    -- Remaining rectangle vertices 
    rectNWLat   NUMBER;
    rectNWLon   NUMBER;
    rectSELat   NUMBER;
    rectSELon   NUMBER;

    rectHeight  NUMBER;
    rectWIdth   NUMBER;

    circleDistanceLat   NUMBER;
    circleDistanceLon   NUMBER;
    cornerDistanceSQ    NUMBER;

BEGIN
    -- Initialization of remaining rectangle vertices  
    rectNWLat := rectNELat;
    rectNWLon := rectSWLon;
    rectSELat := rectSWLat;
    rectSELon := rectNELon;

    -- Rectangle sides length calculation
    rectHeight := calc_distance(rectSWLat, rectSWLon, rectNWLat, rectNWLon);
    rectWidth := calc_distance(rectSWLat, rectSWLon, rectSELat, rectSELon);

    circleDistanceLat := abs( (circleCenterLat * kmToDegreeConst) - ((rectSWLat * kmToDegreeConst) + (rectHeight/2)) );
    circleDistanceLon := abs( (circleCenterLon * kmToDegreeConst) - ((rectSWLon * kmToDegreeConst) + (rectWidth/2)) );

    IF circleDistanceLon > ((rectWidth/2) + circleRadius) THEN
        RETURN -1;   --  -1 => NO Collision ; 0 => Collision Detected
    END IF;

    IF circleDistanceLat > ((rectHeight/2) + circleRadius) THEN
        RETURN -1;   --  -1 => NO Collision ; 0 => Collision Detected
    END IF;

    IF circleDistanceLon <= (rectWidth/2) THEN
        RETURN 0;   --  -1 => NO Collision ; 0 => Collision Detected
    END IF;

    IF circleDistanceLat <= (rectHeight/2) THEN
        RETURN 0;   --  -1 => NO Collision ; 0 => Collision Detected
    END IF;


    cornerDistanceSQ := POWER(circleDistanceLon - (rectWidth/2), 2) + POWER(circleDistanceLat - (rectHeight/2), 2);

    IF cornerDistanceSQ <=  POWER(circleRadius, 2) THEN
        RETURN 0;  --  -1 => NO Collision ; 0 => Collision Detected
    ELSE
        RETURN -1;  --  -1 => NO Collision ; 0 => Collision Detected
    END IF;

    RETURN -1;  --  -1 => NO Collision ; 0 => Collision Detected
END;    
fl4l
sumber
0

Bekerja, baru saja mengetahui hal ini seminggu yang lalu, dan baru saja mengujinya.

double theta = Math.atan2(cir.getX()-sqr.getX()*1.0,
                          cir.getY()-sqr.getY()*1.0); //radians of the angle
double dBox; //distance from box to edge of box in direction of the circle

if((theta >  Math.PI/4 && theta <  3*Math.PI / 4) ||
   (theta < -Math.PI/4 && theta > -3*Math.PI / 4)) {
    dBox = sqr.getS() / (2*Math.sin(theta));
} else {
    dBox = sqr.getS() / (2*Math.cos(theta));
}
boolean touching = (Math.abs(dBox) >=
                    Math.sqrt(Math.pow(sqr.getX()-cir.getX(), 2) +
                              Math.pow(sqr.getY()-cir.getY(), 2)));
pengguna3026859
sumber
Mungkin bekerja untuk Circle-Square, tetapi pertanyaannya adalah tentang Circle-Rectangle.
martineau
0
def colision(rect, circle):
dx = rect.x - circle.x
dy = rect.y - circle.y
distance = (dy**2 + dx**2)**0.5
angle_to = (rect.angle + math.atan2(dx, dy)/3.1415*180.0) % 360
if((angle_to>135 and angle_to<225) or (angle_to>0 and angle_to<45) or (angle_to>315 and angle_to<360)):
    if distance <= circle.rad/2.+((rect.height/2.0)*(1.+0.5*abs(math.sin(angle_to*math.pi/180.)))):
        return True
else:
    if distance <= circle.rad/2.+((rect.width/2.0)*(1.+0.5*abs(math.cos(angle_to*math.pi/180.)))):
        return True
return False
Cassiano
sumber
-2

Dengan asumsi Anda memiliki empat tepi persegi panjang periksa jarak dari tepi ke pusat lingkaran, jika kurang dari jari-jari, maka bentuknya berpotongan.

if sqrt((rectangleRight.x - circleCenter.x)^2 +
        (rectangleBottom.y - circleCenter.y)^2) < radius
// then they intersect

if sqrt((rectangleRight.x - circleCenter.x)^2 +
        (rectangleTop.y - circleCenter.y)^2) < radius
// then they intersect

if sqrt((rectangleLeft.x - circleCenter.x)^2 +
        (rectangleTop.y - circleCenter.y)^2) < radius
// then they intersect

if sqrt((rectangleLeft.x - circleCenter.x)^2 +
        (rectangleBottom.y - circleCenter.y)^2) < radius
// then they intersect
Untuk kebaikan Anda
sumber
Bagaimana dengan kasus di mana lingkaran kecil seluruhnya tertutup oleh persegi panjang besar? Tentunya itu persimpangan, dan akan gagal dalam jawaban ini.
Ken Paul
Ah ya, saya tidak memikirkan itu. Anda bisa menambahkan lebih banyak cek seperti jika sqrt ((rectangleRight.x / 2 - circleCenter.x) ^ 2 + (rectangleBottom.y / 2 - circleCenter.y) ^ 2) <radius maka mereka berpotongan Ini akan panjang dan lambat, tapi dari atas kepala saya itulah yang terbaik yang bisa saya dapatkan.
ForYourOwnGood
Mereka dapat berpotongan pada titik [tunggal] pada salah satu sisi. Anda harus menemukan jarak tepi-pusat juga. (Oh, dan panggil sudut Anda "sudut" :)
aib
Tampaknya ini hanya mendeteksi ketika sudut berada di dalam lingkaran.
stark
-2

Jika persegi panjang berpotongan dengan lingkaran, satu atau lebih titik sudut persegi panjang harus berada di dalam lingkaran. Misalkan empat titik persegi panjang adalah A, B, C, D. setidaknya salah satu dari mereka harus memotong lingkaran. jadi jika jarak dari satu titik ke pusat lingkaran kurang dari jari-jari lingkaran itu harus memotong lingkaran. Untuk mendapatkan jarak Anda dapat menggunakan teorema Pythagoras,

H^2 = A^2 + B^2

Teknik ini memiliki beberapa batasan. Tapi itu akan bekerja lebih baik untuk para pengembang game. terutama deteksi tabrakan

Ini adalah pembaruan yang bagus untuk Algoritma Arvo

Md. Islam Ashraful
sumber
Jawaban ini sangat salah ketika persegi panjang memiliki sisi yang lebih besar dari jari-jari lingkaran.
Paul K