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

Так как в цифровом мире реальное столкновение почти никогда не происходит, мы всегда будем иметь ситуацию, когда "сталкивающийся" круг пересекает прямоугольник.

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

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

Я бы решил эту проблему с апостериорный подход (в двух измерениях).

Короче, я должен решить это уравнение для t:

enter image description here

Где:

  • t - это число, которое отвечает на вопрос: сколько кадров назад столкновение происходит отлично?

  • r - радиус круга.

  • (x, y) - центр круга

  • (v.x, v.y) - его скорость.

  • A(t) и B(t) - это функции, возвращающие координаты x и y точка, в которой сталкиваются круг и прямоугольник (когда круг в позиции (x - t * v.x, y - t * v.y), которая находится в положении, в котором отлично сталкиваются с прямоугольником).

Недавно я решил проблему для столкновений между кругами, но теперь я не знаю закон функций A и B.

Ответ 1

После долгих лет рассмотрения этой проблемы и никогда не придумал идеального решения, я, наконец, сделал это!

Это довольно простой алгоритм прямой обработки, не требуется цикл и аппроксимации.

Вот как это работает на более высоком уровне:

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

И теперь к деталям gory!

Ввод функции ограничен (имеет левый, верхний, правый, нижний) и текущую точку (начало) и будущую точку (конец).

Вывод - это класс, называемый пересечением, который имеет x, y, time, nx и ny.

  • {x, y} - центр круга во время пересечения.
  • время - это значение от 0 до 1, где 0 находится в начале, а 1 - в конце
  • {nx, ny} - нормаль, используемая для отражения скорости для определения новой скорости круга

Мы начинаем с часто используемых кеширующих переменных:

float L = bounds.left;
float T = bounds.top;
float R = bounds.right;
float B = bounds.bottom;
float dx = end.x - start.x;
float dy = end.y - start.y;

И вычисление времени пересечения с каждой боковой плоскостью (если вектор между началом и концом проходит по этой плоскости):

float ltime = Float.MAX_VALUE;
float rtime = Float.MAX_VALUE;
float ttime = Float.MAX_VALUE;
float btime = Float.MAX_VALUE;

if (start.x - radius < L && end.x + radius > L) {
   ltime = ((L - radius) - start.x) / dx;
}
if (start.x + radius > R && end.x - radius < R) {
   rtime = (start.x - (R + radius)) / -dx;
}
if (start.y - radius < T && end.y + radius > T) {
   ttime = ((T - radius) - start.y) / dy;
}
if (start.y + radius > B && end.y - radius < B) {
   btime = (start.y - (B + radius)) / -dy;
}

Теперь мы пытаемся увидеть, строго ли это боковое пересечение (а не угол). Если точка столкновения лежит сбоку, верните пересечение:

if (ltime >= 0.0f && ltime <= 1.0f) {
   float ly = dy * ltime + start.y;
   if (ly >= T && ly <= B) {
      return new Intersection( dx * ltime + start.x, ly, ltime, -1, 0 );
   }
}
else if (rtime >= 0.0f && rtime <= 1.0f) {
   float ry = dy * rtime + start.y;
   if (ry >= T && ry <= B) {
      return new Intersection( dx * rtime + start.x, ry, rtime, 1, 0 );
   }
}

if (ttime >= 0.0f && ttime <= 1.0f) {
   float tx = dx * ttime + start.x;
   if (tx >= L && tx <= R) {
      return new Intersection( tx, dy * ttime + start.y, ttime, 0, -1 );
   }
}
else if (btime >= 0.0f && btime <= 1.0f) {
   float bx = dx * btime + start.x;
   if (bx >= L && bx <= R) {
      return new Intersection( bx, dy * btime + start.y, btime, 0, 1 );
   }
}

Мы дошли так далеко, чтобы мы знали, что там нет пересечения, или он столкнулся с углом. Нам нужно определить угол:

float cornerX = Float.MAX_VALUE;
float cornerY = Float.MAX_VALUE;

if (ltime != Float.MAX_VALUE) {
   cornerX = L;
} else if (rtime != Float.MAX_VALUE) {
   cornerX = R;
}

if (ttime != Float.MAX_VALUE) {
   cornerY = T;
} else if (btime != Float.MAX_VALUE) {
   cornerY = B;
}

// Account for the times where we don't pass over a side but we do hit it corner
if (cornerX != Float.MAX_VALUE && cornerY == Float.MAX_VALUE) {
   cornerY = (dy > 0.0f ? B : T);
}

if (cornerY != Float.MAX_VALUE && cornerX == Float.MAX_VALUE) {
   cornerX = (dx > 0.0f ? R : L);
}

Теперь у нас достаточно информации для решения для треугольника. Это использует формулу расстояния, находя угол между двумя векторами и закон синусов (дважды):

double inverseRadius = 1.0 / radius;
double lineLength = Math.sqrt( dx * dx + dy * dy );
double cornerdx = cornerX - start.x;
double cornerdy = cornerY - start.y;
double cornerdist = Math.sqrt( cornerdx * cornerdx + cornerdy * cornerdy );
double innerAngle = Math.acos( (cornerdx * dx + cornerdy * dy) / (lineLength * cornerdist) );
double innerAngleSin = Math.sin( innerAngle );
double angle1Sin = innerAngleSin * cornerdist * inverseRadius;

// The angle is too large, there cannot be an intersection
if (Math.abs( angle1Sin ) > 1.0f) {
   return null;
}

double angle1 = Math.PI - Math.asin( angle1Sin );
double angle2 = Math.PI - innerAngle - angle1;
double intersectionDistance = radius * Math.sin( angle2 ) / innerAngleSin;

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

// Solve for time
float time = (float)(intersectionDistance / lineLength);

// If time is outside the boundaries, return null. This algorithm can 
// return a negative time which indicates the previous intersection. 
if (time > 1.0f || time < 0.0f) {
   return null;
}

// Solve the intersection and normal
float ix = time * dx + start.x;
float iy = time * dy + start.y;
float nx = (float)((ix - cornerX) * inverseRadius);
float ny = (float)((iy - cornerY) * inverseRadius);

return new Intersection( ix, iy, time, nx, ny );

Woo! Это было весело... у этого есть много возможностей для улучшения, насколько эффективность идет. Вы можете переупорядочить проверку пересечения сторон, чтобы убежать как можно раньше, делая как можно меньше расчетов.

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

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

Intersection inter = handleIntersection( bounds, start, end, radius );

if (inter != null) 
{
   // Project Future Position
   float remainingTime = 1.0f - inter.time;
   float dx = end.x - start.x;
   float dy = end.y - start.y;
   float dot = dx * inter.nx + dy * inter.ny;
   float ndx = dx - 2 * dot * inter.nx;
   float ndy = dy - 2 * dot * inter.ny;
   float newx = inter.x + ndx * remainingTime;
   float newy = inter.y + ndy * remainingTime;
   // new circle position = {newx, newy}
 }

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

Example

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

EDIT: Я изменил код в pastebin, чтобы также включить точку столкновения, а также сделал некоторые улучшения скорости.

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

EDIT: Я изменил код на pastebin, чтобы выйти раньше, если ограничивающий объем пути круга не пересекается с прямоугольником.

Ответ 2

Найти момент контакта не слишком сложно:

Вам нужно положение круга и прямоугольника с отметкой времени перед столкновением (B) и временной отметкой после (A). Вычислите расстояние от центра круга до линии прямоугольника, с которым он сталкивается в моменты времени А и В (т.е. Общая формула для расстояния от точки до линии), а затем время столкновения:

tC = dt*(dB-R)/(dA+dB),

где tC - время столкновения, dt - временной интервал, дБ - расстояние до линии до столкновения, dA - расстояние после столкновения, R - радиус круга.

Это предполагает, что все локально линейно, т.е. ваши временные метки достаточно малы, и поэтому скорость и т.д. не меняются сильно по времени, когда вы вычисляете столкновение. Это, в конце концов, точка временных меток: в этом случае с небольшим временным временем нелинейные задачи локально линейны. В приведенном выше уравнении я использую следующее: dB-R - это расстояние от круга до линии, а dA + dB - общее перемещение расстояния, поэтому этот вопрос просто приравнивает отношение расстояния к коэффициенту времени, предполагая, что все приблизительно линейно в пределах времени. (Конечно, в момент столкновения линейное приближение не является лучшим, но для нахождения момента столкновения вопрос заключается в том, является ли он линейным в пределах времени до момента столкновения.)

Ответ 3

Это нелинейная проблема, правильно?

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

Предполагаете ли вы, что шары и прямоугольники являются жесткими, без деформации? Контакт с трением? Как вы справитесь с движением мяча после того, как будет сделан контакт? Переходите ли вы к системе координат контакта (нормальный + тангенциальный), вычисляя, а затем преобразовывая назад?

Это не тривиальная проблема.

Возможно, вам стоит заглянуть в физический движок, например Box2D, а не самостоятельно его кодировать.