Как определить направление столкновения двухмерных прямоугольных объектов?

11

После этого вопроса мне нужна дополнительная помощь.

Как я могу узнать, с какой стороны прямоугольника произошло столкновение, и реагировать соответствующим образом?

прямоугольники сталкиваются со всех сторон

Синие стрелки - это пути, по которым будут следовать некоторые круглые объекты, если до и после столкновения с рамкой.

Как я могу рассчитать это?

NemoStein
источник

Ответы:

8

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

Сначала вы строите прямоугольник текущего объекта со следующими значениями:

int boxLeft = box.X;
int boxRight = boxLeft + box.Width;
int boxTop = box.Y;
int boxBottom = boxTop + box.Height;

Далее у вас должна быть позиция старого объекта (которую вы можете сохранить на каждом объекте или просто передать функции), чтобы создать прямоугольник старого объекта (когда он не сталкивался):

int oldBoxLeft = box.OldX;
int oldBoxRight = oldBoxLeft + box.Width;
int oldBoxTop = box.OldY;
int oldBoxBottom = oldBoxTop + box.Height;

Теперь, чтобы узнать, откуда произошло столкновение, вы должны найти сторону, где старая позиция не находилась в зоне столкновения и где находится ее новая позиция. Потому что, когда вы думаете об этом, это то, что происходит, когда вы сталкиваетесь: сторона, которая не сталкивалась, входит в другой прямоугольник.

Вот как вы могли бы это сделать (эти функции предполагают наличие коллизии. Их не следует вызывать, если коллизии нет):

 bool collidedFromLeft(Object otherObj)
{
    return oldBoxRight < otherObj.Left && // was not colliding
           boxRight >= otherObj.Left;
}

Ринс и повторить.

bool collidedFromRight(Object otherObj)
{
    return oldBoxLeft >= otherObj.Right && // was not colliding
           boxLeft < otherObj.Right;
}

bool collidedFromTop(Object otherObj)
{
    return oldBoxBottom < otherObj.Top && // was not colliding
           boxBottom >= otherObj.Top;
}

bool collidedFromBottom(Object otherObj)
{
    return oldBoxTop >= otherObj.Bottom && // was not colliding
           boxTop < otherObj.Bottom;
}

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

if (collidedFromTop(otherObj) || collidedFromBottom(otherObj))
    obj.Velocity.Y = -obj.Velocity.Y;
if (collidedFromLeft(otherObj) || collidedFromRight(otherObj))
    obj.Velocity.X = -obj.Velocity.X;

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

Джесси Эмонд
источник
Еще раз, вы были правы! ; D Спасибо ... (присылайте мне больше открыток с вашей доски в следующий раз ... ^ ___ ^)
NemoStein
Аааа, к сожалению, я не знал, для чего я мог бы использовать это ... может быть, в следующий раз!
Джесси Эмонд
7

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


Давайте определим контекст и некоторые переменные, чтобы сделать следующее объяснение более понятным. Форма представления, которую мы будем использовать здесь, вероятно, не соответствует форме ваших собственных данных, но это должно быть проще для понимания (на самом деле вы можете использовать следующие методы, используя представления другого типа, как только вы поймете принцип)

Итак, мы рассмотрим выровненную ограничивающую ось (или ориентированную ограниченную рамку ) и движущуюся сущность .

  • Ограничительная рамка состоит из 4 сторон, и мы определим каждую из них как:
    Side1 = [x1, y1, x2, y2] (две точки [x1, y1] и [x2, y2])

  • Движущийся объект определяется как вектор скорости (позиция + скорость):
    позиция [posX, posY] и скорость [speedX, speedY] .


Вы можете определить, какая сторона AABB / OBB поражена вектором, используя следующий метод:

  • 1 / Найти точки пересечения между бесконечными линиями, проходящими через четыре стороны AABB, и бесконечной линией, проходящей через позицию объекта (предварительное столкновение), которые используют вектор скорости объекта как наклон. (Вы можете найти точку столкновения или неопределенное число, которое соответствует параллелям или перекрывающимся линиям)

  • 2 / Как только вы знаете точки пересечения (если они существуют), вы можете искать те, которые находятся в границах сегмента.

  • 3 / Наконец, если в списке все еще есть несколько точек (вектор скорости может проходить через несколько сторон), вы можете искать ближайшую точку от начала объекта, используя величины вектора от пересечения до начала объекта.

Тогда вы можете определить угол столкновения с помощью простого точечного произведения.

  • 4 / Найти угол между столкновениями, используя точечное произведение вектора сущности (вероятно, шара?) С вектором стороны удара.

----------

Больше деталей:

  • 1 / Найти пересечения

    • a / Определить бесконечные линии (Ax + Bx = D), используя их параметрические формы (P (t) = Po + tD).

      Начало координат: Po = [posX, posY]
      Вектор направления: D = [speedX, speedY]

      A = Dy = speedY
      B = -Dx = -скорость
      D = (Po.x * Dy) - (Po.y * Dx) = (posX speedY) - (posY speedX)

      Ax + By = D <====> (speedY x) + (-скорость Y) = (posX speedY) - (posY speedX)

      Я использовал значения точек сущности, чтобы проиллюстрировать метод, но это точно такой же метод, чтобы определить 4 боковые бесконечные линии ограничительной рамки (используйте Po = [x1, y1] и D = [x2-x1; y2-y1] вместо).

    • b / Далее, чтобы найти пересечение двух бесконечных линий, мы можем решить следующую систему:

      A1x + B1x = D1 <== Линия, проходящая через точку объекта с вектором скорости в виде спада.
      A2x + B2x = D2 <== Одна из линий, проходящих через стороны AABB.

      который дает следующие координаты для перехвата:

      Перехват x = (( B 2 * D 1) - ( B 1 * D 2)) / (( A 1 * B 2) - ( A 2 * B 1))
      Перехват y = (( A 1 * D 2) - ( A 2 * D 1)) / (( A 1 * B 2) - ( A 2 * B 1))

      Если знаменатель ((A1 * B2) - (A2 * B1)) равен нулю, то обе линии параллельны или пересекаются, в противном случае вы должны найти пересечение.

  • 2 / Проверка границ сегмента. Поскольку это легко проверить, нет необходимости в дополнительных деталях.

  • 3 / Поиск ближайшей точки. Если в списке все еще есть несколько точек, мы можем найти, какая сторона является ближайшей к исходной точке объекта.

    • a / Определить вектор, идущий от точки пересечения к точке происхождения объекта

      V = Po - Int = [Po.x - Int.x; Po.y - Int.y]

    • б / Рассчитать векторную величину

      || V || = sqrt (V.x² + V.y²)

    • с / найти самый маленький.
  • 4 / Теперь, когда вы знаете, какая сторона будет поражена, вы можете определить угол, используя точечное произведение.

    • а / Пусть S = [х2-х1; у2-у1] будет боковой вектор , который будет удар и Е = [SpeedX; Speedy] будет вектор скорости объекта.

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

      S · E = Sx Ex + Sy Ey
      и
      S · E = || S || || E || cos θ

      Таким образом, мы можем определить θ, немного манипулируя этим уравнением ...

      cos θ = (S · E) / (|| S || || E ||)

      θ = acos ((S · E) / (|| S || || E ||)))

      с

      S · Е = Sx * Ех + Sy * Еу
      || S || = SQRT (Sx² + Sy²)
      || Е || = SQRT (Ex² + Ey²)


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

Я не проверял конкретный пример OBB (я сделал с AABB), но он должен работать тоже.

Valkea
источник
6

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

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

Анко
источник
2

Одним из способов было бы вращать мир вокруг вашего прямоугольника. «Мир» в данном случае - это только те объекты, которые вас волнуют: прямоугольник и шар. Вы вращаете прямоугольник вокруг его центра до тех пор, пока его границы не выровняются с осями x- / y, а затем вращаете шар на ту же величину.

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

Тогда вы можете легко проверить на столкновение так же, как и с любым другим не повернутым прямоугольником.


Другим вариантом является обработка прямоугольника как четырех отдельных отрезков и проверка на столкновение с каждым из них в отдельности. Это позволяет вам проверить на столкновение и выяснить, какая сторона столкнулась одновременно.

BlueRaja - Дэнни Пфлугхофт
источник
1

Я использовал фиксированные углы в моих расчетах, но это должно помочь вам

void Bullet::Ricochet(C_Rect *r)
{
    C_Line Line;
    //the next two lines are because I detected 
    // a collision in my main loop so I need to take a step back.

    x = x + ceil(speed * ((double)fcos(itofix(angle)) / 65536));
    y = y + ceil(speed * ((double)fsin(itofix(angle)) / 65536));
    C_Point Prev(x,y);

    //the following checks our position to all the lines will give us
    // an answer which line we will hit due to no lines
    // with angles > 90 lines of a rect always shield the other lines.

    Line = r->Get_Closest_Line(Prev);    
    int langle = 0;
    if(!Line.Is_Horizontal())   //we need to rotate the line to a horizontal position
    {
        langle = Line.Get_Point1().Find_Fixed_Angle(Line.Get_Point2());
        angle = angle - langle;  //to give us the new angle of approach
    }
    //at this point the line is horizontal and the bullet is ready to be fixed.
    angle = 256 - angle;
    angle += langle;
}
Дэвид Sopala
источник