Виявлення зіткнення (перетин) кола-прямокутника


192

Як я можу сказати, чи перетинаються коло та прямокутник у 2D евклідовому просторі? (тобто класична двовимірна геометрія)


1
Чи прямокутник завжди вирівняний з осями, чи його можна обертати довільним кутом?
e.James

11
@eJames: як це має значення? Ви перевіряєте прямокутник на перетин з колом ; Ви завжди можете перетворити свою систему координат так, щоб прямокутник був вісь-паралельний, без зміни кола :-)
ShreevatsaR

Ви повинні додати, що як відповідь, обертаючись на -Θ і все ...
аїб

2
@ShreevatsaR: Це важливо з точки зору того, чи не потрібно мені турбуватися про переклад координат чи ні. @aib: О дорогий!
e.James

Відповіді:


191

Є лише два випадки, коли коло перетинається з прямокутником:

  • Або центр кола лежить всередині прямокутника, або
  • Один з країв прямокутника має точку в колі.

Зауважте, що для цього не потрібно, щоб прямокутник був паралельним осі.

Деякі різні способи коло та прямокутник можуть перетинатися

(Один із способів побачити це: якщо жоден з ребер не має точки в колі (якщо всі ребра повністю «поза» кола), єдиний спосіб, коли коло все ще може перетинати полігон, це якщо він повністю лежить всередині багатокутник.)

З цим розумінням, що - щось подібне до наступного буде працювати, де коло має центр Pі радіус R, а прямокутник має вершини A, B, C,D в такому порядку (не повний код):

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)))

Якщо ви пишете будь-яку геометрію, ви, мабуть, вже маєте вищевказані функції у своїй бібліотеці. В іншому випадку pointInRectangle()може бути реалізований декількома способами; будь-яка із загальних точок багатокутника методах буде працювати, але для прямокутника ви можете просто перевірити, чи працює це:

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

І intersectCircle() це також легко здійснити: одним із способів було б перевірити, чи підніжжя перпендикуляра від Pлінії є достатньо близько та між кінцевими точками, і перевірити кінцеві точки в іншому випадку.

Прикольне те, що ця сама ідея працює не просто для прямокутників, а для перетину кола з будь-яким простим багатокутником - навіть не повинно бути опуклим!


25
Що я того вартий, я справді думаю, що ця відповідь краща за мою. Дві основні причини: 1: він не потребує повороту, якщо прямокутник не є вісь-паралельний, і, 2: концепція легко поширюється на всі багатокутники.
e.James

2
@paniq: Ну, обидва - це постійний час. :-) Але так, це корисніше як загальне рішення, що охоплює прямокутники з будь-якою орієнтацією, а насправді будь-який простий багатокутник.
ShreevatsaR

7
як щодо випадку, коли прямокутник знаходиться повністю всередині кола, але центр кола не знаходиться в межах прямокутника?
ericsoco

2
@ericsoco: Добре спостереження. :-) Я думаю, я мав би сказати, що "перетинає диск" у "одному з країв прямокутника перетинає коло", тому що я мав на увазі, що він розділяє точку з самим колом, не обов'язково межами кола. У будь-якому випадку, опис, наведений вище, "перевірте, чи стопа перпендикуляра від P [центру кола] до лінії є достатньо близькою та між кінцевими точками, і перевірте кінцеві точки в іншому випадку" все ще спрацює - наприклад, кінцеві точки лежать усередині кола ( диск).
ShreevatsaR

2
@ DexD.Hunter Якщо центр кола знаходиться поза прямокутником, але частина його знаходиться всередині прямокутника, то обов’язково один з країв прямокутника перетинає коло.
ShreevatsaR

289

Ось як я це зробив:

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));
}

Ось як це працює:

ілюзорність

  1. Перша пара ліній обчислює абсолютні значення різниці x і y між центром кола і центром прямокутника. Це згортає чотири квадрати вниз, в один, так що обчислення не потрібно робити чотири рази. На зображенні показана область, в якій зараз повинен лежати центр кола. Зверніть увагу, що показано лише один квадрант. Прямокутник є сірою областю, а червона рамка окреслює критичну область, яка знаходиться рівно на один радіус від країв прямокутника. Центр кола повинен бути в межах цієї червоної межі, щоб відбулося перехрестя.

  2. Друга пара ліній усуває легкі випадки, коли коло знаходиться досить далеко від прямокутника (в будь-якому напрямку), що жодне перетин не можливе. Це відповідає зеленій зоні на зображенні.

  3. Третя пара ліній стосується легких випадків, коли коло достатньо близько до прямокутника (в будь-якому напрямку), що гарантовано перетин. Це відповідає помаранчевому та сірому ділянкам зображення. Зауважте, що цей крок повинен бути виконаний після кроку 2, щоб логіка мала сенс.

  4. Решта рядків обчислюють складний випадок, коли коло може перетинати кут прямокутника. Щоб вирішити, обчисліть відстань від центру кола та кута, а потім перевірте, що відстань не більше радіуса кола. Цей обчислення повертає хибність для всіх кіл, центр яких знаходиться в межах затіненої червоною точкою, і повертає істинність для всіх кіл, центр яких знаходиться в зоні білого відтінку.


4
Дуже хороша! Примітки: мабуть, тут rect.x / y знаходиться у правому верхньому куті прямокутника. Також ви можете усунути дорогий квадратний корінь, порівнявши його порівняно з квадратом радіуса.
luqui

2
О ні, моя погана. rect.x / y знаходиться в лівій нижній частині прямокутника. Я б написав: circleDistance.x = abs (circle.x - (rect.x + rect.width / 2));
luqui

2
@Tanner: Йдемо. Ура для резервного копіювання та OCD;)
e.James

11
просто для уточнення - ця відповідь стосується лише прямокутників, що вирівнюються по осі. це зрозуміло з прочитання коментарів до інших відповідей, але не очевидно з цієї відповіді + коментарів. (чудова відповідь для
вирівнених прямокутних прямих тхо

3
Чудово! Читачам важливо знати, що тут я вважаю, що визначення rect є rect.x, а rect.y є центром прямої. У моєму світі ректовий xy вгорі / зліва від rect, а 0,0 - вгорі / зліва від екрана, тому я використав:circleDistance_x = abs(circle.x - (rect.x-rect.w/2)); circleDistance_y = abs(circle.y - (rect.y-rect.h/2));
erco

123

Ось ще одне рішення, яке досить просто здійснити (і досить швидко теж). Він зачепить усі перехрестя, в тому числі, коли сфера повністю увійшла до прямокутника.

// 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);

З будь-якою гідною бібліотекою з математики її можна скоротити до 3 або 4 рядків.


3
У вас є помилка, ви шукаєте найближчийY зліва та справа, а не вгорі та знизу, інакше прекрасне рішення.
manveru

8
Мені подобається ця відповідь найкраще. Це короткий, легкий для розуміння та швидкий.
Джон Курлак

2
Я думаю, що ваше рішення не вдається, якщо прямокутник косий осі x- і y.
Лев

3
@Leo Я думаю, що не важко змінити цей алгоритм для відповідності цьому випадку, слід просто застосувати перетворення координат, де походження знаходиться в центрі прямокутника, а прямокутник вже не косий. Перетворення потрібно застосувати лише до центру кола.
Енобайрам

1
Це в основному те саме, що і код, знайдений на migapro.com/circle-and-rotated-rectangle-collision-detection, який я також переніс до Objective-C. Працює дуже добре; це приємне рішення проблеми.
PKCLsoft

10

ваша сфера та пряма перетинають IIF,
відстань між центром кола та однією вершиною прямої ваги менша за радіус вашої сфери
АБО
відстань між центром кола та одним краєм вашої прямої є меншою за радіус вашої сфери ( [ відстань точкової лінії ])
АБО
центр кола знаходиться всередині прямої

відстані точки:

P1 = [x1, y1]
P2 = [x2, y2]
Відстань = sqrt (abs (x1 - x2) + abs (y1-y2))

відстань від точки:

L1 = [x1, y1], L2 = [x2, y2] (дві точки вашої лінії, тобто точки вершин)
P1 = [px, py] деяка точка

Відстань d = abs ((x2-x1) (y1-py) - (x1-px) (y2-y1)) / Відстань (L1, L2)


центр кола всередині прямої:
візьміть відокремлювальну вісь наближення: якщо є проекція на лінію, яка відокремлює прямокутник від точки, вони не перетинаються

Ви проектуєте точку на лінії, паралельні сторонам прямої кишки, а потім можете легко визначити, чи перетинаються вони. якщо вони перетинаються не на всіх 4 проекціях, вони (точка і прямокутник) не можуть перетинатися.

вам просто потрібен внутрішній продукт (x = [x1, x2], y = [y1, y2], x * y = x1 * y1 + x2 * y2)

ваш тест виглядав би так:

// краї прямокутника: TL (верхній лівий), TR (верхній правий), BL (знизу лівий), BR (нижній правий)
// Точка на тест: POI

відокремлений = хибний
для egde в {{TL, TR}, {BL, BR}, {TL, BL}, {TR-BR}}: // краї
    D = край [0] - край [1]
    innerProd = D * POI
    Interval_min = min (D * край [0], D * край [1])
    Interval_max = max (D * край [0], D * край [1])
    якщо ні (Interval_min ≤ innerProd ≤ Interval_max) 
           відокремлений = правда
           перерва // кінець для циклу 
    закінчується, якщо
кінець для
якщо (відокремлено істинно)    
      повернути "немає перехрестя"
ще 
      повернути "перехрестя"
закінчується, якщо

це не передбачає прямокутника, орієнтованого на вісь, і легко розширюється для тестування перетинів між опуклими множинами.


1
Чи не повинно вказувати відстань від точки до точки, використовуючи квадрат, а не абс?
Томас

6

Це найшвидше рішення:

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;
}

Зверніть увагу на порядок виконання, а половина ширини / висоти попередньо обчислюється. Також квадратування проводиться "вручну", щоб зберегти деякі цикли годин.


3
Я не думаю, що ви можете стверджувати, що п’ять тестів / порівнянь у найдорожчому кодовому шляху - це "найшвидше рішення" без доказів.
sam hocevar


1
З мого досвіду з цим методом, зіткнення не відбувається більшу частину часу. Тому тести будуть викликати вихід раніше, ніж більша частина коду буде виконана.
intrepidis

6

Найпростіше рішення, яке я придумав, досить просте.

Він працює, знаходячи точку в прямокутнику, найближчій до кола, а потім порівнюючи відстань.

Ви можете зробити все це за допомогою декількох операцій і навіть уникнути функції 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;
}

І це все! Вищевказане рішення передбачає початок у верхньому лівому куті світу, а вісь x спрямована вниз.

Якщо ви хочете вирішити проблеми зіткнення між рухомим колом і прямокутником, це набагато складніше і висвітлено іншу мою відповідь.


Це не зможе виявити перехрестя, якщо радіус кола занадто малий і його центр знаходиться всередині прямокутника!
Йоав

2
Чи можете ви надати фактичний внесок, який робить це невдалим? Коли коло знаходиться всередині, ліва частина тесту дорівнює 0,0. Якщо радіус не дорівнює нулю, права частина тесту повинна бути> 0,0
ClickerMonkey

Чи буде це працювати і для обертових прямокутників? якщо ні, то, будь ласка, підкажіть про це .....
М Абдул Самі

4

Власне, це набагато простіше. Вам потрібно лише дві речі.

Спочатку потрібно знайти чотири ортогональних відстані від центру кола до кожної лінії прямокутника. Тоді ваше коло не буде перетинати прямокутник, якщо будь-які три з них більші за радіус кола.

По-друге, вам потрібно знайти відстань між центром кола та центром прямокутника, тоді коло ви не будете всередині прямокутника, якщо відстань більша половини довжини діагоналі прямокутника.

Удачі!


3

Ось мій код C для вирішення зіткнення між сферою та полем, не вирівненим по осі. Він покладається на пару моїх власних процедур бібліотеки, але може виявитися корисним для деяких. Я використовую його в грі, і вона чудово працює.

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;
}

2

Для візуалізації візьміть цифрову клавіатуру. Якщо ключ "5" представляє ваш прямокутник, то всі клавіші 1-9 представляють 9 квадрантів простору, розділених на лінії, що складають ваш прямокутник (5 - це внутрішня частина).

1) Якщо центр кола знаходиться в квадранті 5 (тобто всередині прямокутника), то дві форми перетинаються.

Якщо це не виходить, є два можливі випадки: а) Коло перетинається з двома або більше сусідніми краями прямокутника. б) Коло перетинається з одним краєм прямокутника.

Перший випадок простий. Якщо коло перетинається з двома сусідніми краями прямокутника, він повинен містити кут, що з'єднує ці два краї. (Це або його центр лежить у квадранті 5, який ми вже висвітлювали. Також зауважимо, що випадок, коли коло перетинається лише з двома протилежними краями прямокутника, охоплюється також.)

2) Якщо будь-який з кутів A, B, C, D прямокутника лежить всередині кола, то дві форми перетинаються.

Другий випадок хитріший. Слід зазначити, що це може статися лише тоді, коли центр кола лежить в одному з квадрантів 2, 4, 6 або 8. (Насправді, якщо центр знаходиться на будь-якому з квадрантів 1, 3, 7, 8, відповідний кут буде найближчою точкою до нього.)

Тепер ми маємо випадок, що центр кола знаходиться в одному з «крайових» квадрантів, і він перетинається лише з відповідним ребром. Потім точка на ребрі, яка знаходиться найближче до центру кола, повинна лежати всередині кола.

3) Для кожного рядка AB, BC, CD, DA побудуйте перпендикулярні прямі p (AB, P), p (BC, P), p (CD, P), p (DA, P) через центр кола P. Для кожна перпендикулярна лінія, якщо перетин з початковим краєм лежить всередині кола, то дві форми перетинаються.

Існує ярлик для цього останнього кроку. Якщо центр кола знаходиться в квадранті 8, а край AB - верхній край, то точка перетину матиме y-координати A і B, а x-координату центру P.

Ви можете побудувати чотири перетину ліній і перевірити, чи лежать вони на відповідних краях, або дізнатись, у якому квадранті Р знаходиться, і перевірити відповідний перетин. Обидва повинні спрощуватись до одного булевого рівняння. Будьте обережні, що на етапі 2 вище не виключено, що P опинився в одному з «кутових» квадрантів; воно просто шукало перехрестя.

Редагувати: Як виявилося, я не помітив простого факту, що №2 є підпунктом №3 вище. Адже кути теж є точками по краях. Дивіться відповідь @ ShreevatsaR нижче для великого пояснення. А тим часом забудьте номер 2 вище, якщо ви не хочете швидкої, але зайвої перевірки.


2

Ця функція виявляє зіткнення (перетини) між Колом та Прямокутником. Він працює як метод e.James у своїй відповіді, але цей виявляє зіткнення для всіх кутів прямокутника (не лише правого кута).

ПРИМІТКА:

aRect.origin.x і aRect.origin.y - координати нижнього лівого кута прямокутника!

aCircle.x і aCircle.y - координати Центру кола!

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;
}

1

У мене є метод, який дозволяє уникнути дорогих піфагор, якщо не потрібно - тобто. коли обмежувальні коробки прямокутника і кола не перетинаються.

І він буде працювати і для неевклідового:

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 можна замінити на minY, maxY і те саме для minLon, maxLon: замініть його на minX, maxX
  • normDist - лише трохи швидший метод, ніж розрахунок повної відстані. Наприклад , без квадратного кореня в евклідовому просторі (або без нього багато інших речей для гаверсінуса) dLat=(lat-circleY); dLon=(lon-circleX); normed=dLat*dLat+dLon*dLon. Звичайно, якщо ви використовуєте метод normDist, вам потрібно створити normedDist = dist*dist;коло

Дивіться повний код BBox та Circle мого проекту GraphHopper .


1

Я створив клас для роботи з фігурами, сподіваюся, вам сподобається

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;
  } 
}

1

Ось модифікований код на 100% працює:

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


1

Ось швидкий однолінійний тест для цього:

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

Це вирівняний по осі випадок, коли rect_halvesпозитивний вектор вказує від середини прямокутника до кута. Вираз всередині length()є дельта-вектором від centerнайближчої точки прямокутника. Це працює в будь-якому вимірі.


1
  • Спочатку перевірте, чи прямокутник і квадрат, дотичні до кола перекриваються (легко). Якщо вони не перекриваються, вони не стикаються.
  • Перевірте, чи знаходиться центр кола всередині прямокутника (легко). Якщо вона всередині, вони стикаються.
  • Обчисліть мінімальну відстань у квадраті від сторін прямокутника до центру кола (трохи важко). Якщо нижчий радіус квадрата, то вони стикаються, інакше - ні.

Це ефективно, тому що:

  • Спочатку він перевіряє найпоширеніший сценарій з дешевим алгоритмом, і коли впевнений, що вони не стикаються, він закінчується.
  • Потім він перевіряє наступний найпоширеніший сценарій за допомогою дешевого алгоритму (не обчислюйте квадратний корінь, використовуйте значення квадрата) і коли впевнений, що вони стикаються, він закінчується.
  • Потім він виконує дорожчий алгоритм для перевірки зіткнення з прямокутними межами.

1

працював для мене (робота лише тоді, коли кут прямокутника дорівнює 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;
}

хммм ... Я проголосував за це, але потім перевірив належним чином, і, думаю, це не працює, наприклад, на кутах. Він би працював на два прямокутники.
Дан Дзен

1

Трохи вдосконалюючи відповідь 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);

Це віднімається rect.w / 2і rect.h / 2один раз замість до трьох разів.


Я сильно підозрюю, що більшість сучасних компіляторів (або, принаймні, могли б) автоматично оптимізували зайві обчислення для вас.
martineau

0

Для тих, хто повинен обчислити зіткнення кола / прямокутника в географічних координатах з SQL,
це моя реалізація в Oracle 11 алгоритму, запропонованого e.James .

Для введення потрібні координати кола, радіус кола в км і дві координати вершини прямокутника:

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;    

0

Працює, щойно я вияснив це тиждень тому, і тільки зараз перейшов до тестування.

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)));

Можливо, буде працювати для Circle-Square, але питання стосується Circle-Rectangle.
мартіно

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

-2

Якщо припустити, що у вас є чотири ребра прямокутника, перевірте відстань від країв до центру кола, якщо його менше радіуса, то форми перетинаються.

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

А як щодо випадку, коли мале коло повністю закрите великим прямокутником? Безумовно, це перехрестя, і не вдалося б випробувати тест у цій відповіді.
Кен Пол

Ага так, я не думав про це. Ви можете просто додати більше перевірок, наприклад, якщо sqrt ((rectangleRight.x / 2 - circleCenter.x) ^ 2 + (rectangleBottom.y / 2 - circleCenter.y) ^ 2) <радіус, то вони будуть перетинатися. Це буде довгим і повільним, але з моєї голови це найкраще, що я можу придумати.
ForYourOwnGood

Вони можуть перетинатися в будь-якій [єдиній] точці на будь-якому з ребер. Ви також повинні знайти відстані в центрі краю. (О, і називати свої кути «кути» :)
AIB

Здається, це виявляється лише тоді, коли кут знаходиться всередині кола.
старий

-2

Якщо прямокутник перетинається з колом, одна чи кілька кутових точок прямокутника повинні бути всередині кола. Припустимо, чотири точки прямокутника - A, B, C, D. принаймні одна з них повинна перетинати коло. тому якщо відстань від однієї точки до центру кола менша за радіус кола, вона повинна перетинати коло. Щоб отримати відстань, ви можете використовувати теорему Піфагора,

H^2 = A^2 + B^2

Ця методика має деякі межі. Але це буде краще працювати для розробників ігор. особливо виявлення зіткнень

Це гарне оновлення до Алгоритму Арво


Ця відповідь неймовірно помилкова в будь-який момент, коли прямокутник має сторону, більшу, ніж радіус кола.
Пол К
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.