Обнаружение столкновения окружности с прямоугольником (пересечение)


192

Как я могу определить, пересекаются ли круг и прямоугольник в евклидовом пространстве 2D? (т.е. классическая 2D геометрия)


1
Всегда ли прямоугольник выровнен с осями или его можно повернуть на произвольный угол?
e.James

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

Вы должны добавить это в качестве ответа, вращая через -Θ и все ...
aib

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

Ответы:


192

Есть только два случая, когда круг пересекается с прямоугольником:

  • Либо центр круга лежит внутри прямоугольника, либо
  • Один из краев прямоугольника имеет точку в круге.

Обратите внимание, что для этого не требуется, чтобы прямоугольник был параллельным оси.

Несколько различных способов пересечения круга и прямоугольника

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

С этим пониманием, что - то вроде следующего будет работать, где круг имеет центр 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 Если центр круга находится за пределами прямоугольника, но часть его находится внутри прямоугольника, то обязательно один из краев прямоугольника пересекает круг.
ШриевацаР

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

Вот как это работает:

illusration

  1. Первая пара линий вычисляет абсолютные значения разности x и y между центром круга и центром прямоугольника. Это объединяет четыре квадранта в один, так что вычисления не нужно выполнять четыре раза. На рисунке показана область, в которой теперь должен находиться центр круга. Обратите внимание, что отображается только один квадрант. Прямоугольник - это серая область, а красная граница очерчивает критическую область, которая находится ровно в одном радиусе от краев прямоугольника. Центр круга должен находиться внутри этой красной границы, чтобы произошло пересечение.

  2. Вторая пара линий исключает простые случаи, когда круг находится достаточно далеко от прямоугольника (в любом направлении), и пересечение невозможно. Это соответствует зеленой области на изображении.

  3. Третья пара линий обрабатывает простые случаи, когда окружность достаточно близка к прямоугольнику (в любом направлении), чтобы пересечение гарантировалось. Это соответствует оранжевой и серой полосам на изображении. Обратите внимание, что этот шаг должен быть выполнен после шага 2, чтобы логика имела смысл.

  4. Оставшиеся строки рассчитывают сложный случай, когда круг может пересекать угол прямоугольника. Чтобы решить, вычислите расстояние от центра круга и угла, а затем убедитесь, что расстояние не больше радиуса круга. Это вычисление возвращает false для всех кругов, центр которых находится внутри красной заштрихованной области, и возвращает true для всех кругов, центр которых находится внутри белой заштрихованной области.


4
Очень хорошо! Примечания: очевидно, что rect.x / y находится в верхнем правом углу прямоугольника. Также вы можете исключить дорогой квадратный корень, сравнив его с квадратом радиуса.
Луки

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

2
@Tanner: поехали. Ура для резервных копий и OCD;)
e.James

11
просто чтобы уточнить - этот ответ относится только к выровненным по оси прямоугольникам. это ясно из прочтения комментариев к другим ответам, но не очевидно из этого ответа + только комментарии. (отличный ответ для выровненных по оси тектов!)
ericsoco

3
Большой! Для читателей важно знать, что здесь я считаю, что определение прямоугольника - это rect.x & rect.y - это центр прямоугольника. В моем мире xy прямоугольника находится сверху / слева от прямоугольника, а 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
У вас есть ошибка, вы ищете ближайший с левой и правой, а не сверху и снизу, в противном случае прекрасное решение.
Манверу

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 * edge [0], D * edge [1])
    Interval_max = max (D * edge [0], D * edge [1])
    если нет (Interval_min ≤ innerProd ≤ Interval_max) 
           отделенный = правда
           break // конец цикла 
    конец, если
конец для
если (разделено верно)    
      вернуть "нет пересечения"
еще 
      вернуть "пересечение"
конец, если

это не предполагает выравнивания по оси прямоугольника и легко расширяется для проверки пересечений между выпуклыми наборами.


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
Я не думаю, что вы можете утверждать, что пять тестов / сравнений в самом дорогом пути кода - это «самое быстрое решение» без каких-либо доказательств.
Сэм Хоцевар


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.

Вы можете построить четыре пересечения линий и проверить, лежат ли они на соответствующих ребрах, или выяснить, в каком квадранте 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)));
}

Бассам Алугили


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один раз вместо трех раз.


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

0

Для тех, кто должен вычислить столкновение Круг / Прямоугольник в Географических Координатах с SQL,
это моя реализация в предложенном оракулом 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)));

Может работать на Круг-Квадрат, но вопрос о Круг-Прямоугольнике.
Мартино

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.