Круг внутри круга столкновения

9

В одном из моих проектов у меня есть игровая площадка в форме круга. Внутри этого круга движется еще один маленький круг. Я хочу, чтобы маленький круг не выходил за пределы большего. Ниже вы можете видеть, что в кадре 2 маленький круг частично снаружи, мне нужен способ вернуть его назад, прежде чем он собирается выйти наружу. Как это может быть сделано?

Основной пример

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

То, что я хотел бы сделать, это перед тем, как переместить маленький круг, я предсказываю его следующую позицию, и если он находится снаружи, я нахожу время столкновения между t = 0 и t = 1 (t = 1 шаг полного времени). Если у меня есть время столкновения t, то я просто перемещаю маленький круг в течение t вместо полного временного шага. Но опять же, проблема в том, что я не знаю, как обнаружить в это время столкновение, когда речь идет о двух кругах, и один находится внутри другого.

РЕДАКТИРОВАТЬ:

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

введите описание изображения здесь

dbostream
источник

Ответы:

10

Давайте предположим, что большой круг имеет центр Aи радиус, Rа маленький круг имеет центр Bи радиус, rдвижущийся в направлении местоположения C.

Существует элегантный способ решить эту проблему, используя суммы Минковского (на самом деле вычитания): заменить диск радиуса Rдиском радиуса R-r, а диск радиуса rдиском радиуса 0, т.е. простая точка, расположенная в B. Проблема становится проблемой пересечения линии-круга.

Затем вам просто нужно проверить, ACменьше ли расстояние, чем R-r. Если это так, круги не сталкиваются. Если она больше, просто найти точку Dна BCна расстоянии R-rот Aи это новое расположение центра малого круга. Это эквивалентно нахождению kтакого, что:

  vec(BD) = k*vec(BC)
and
  norm(vec(AD)) = R-r

Подставив vec(AD)с vec(AB) + vec(BD)дает:

AB² + k² BC² + 2k vec(AB).vec(BC) = (R-r

Если исходное положение было внутри большого круга, это квадратное уравнение kимеет один положительный корень. Вот как решить уравнение в псевдокоде:

b = - vec(AB).vec(BC) / BC²    // dot product
c = (AB² - (R-r)²) / BC²
d = b*b - c
k = b - sqrt(d)
if (k < 0)
    k = b + sqrt(d)
if (k < 0)
    // no solution! we must be slightly out of the large circle

При этом значении kновый центр малого круга будет Dтаким, чтобы BD = kBC.

Редактировать : добавить решение квадратного уравнения

Сэм Хоцевар
источник
Спасибо, выглядит элегантно, но я не совсем уверен, что понимаю. Например: «просто найдите точку D на BC на расстоянии Rr от A». Я нарисовал картинку, чтобы лучше понять. Так что, если мы начнем с B (AX, AY- (Rr)), а C - это то место, где мы в итоге окажемся с текущей скоростью. Как я понимаю процитированный текст: найдите точку D на отрезке BC, которая находится на расстоянии Rr от A. Но то, как я вижу ее на рисунке, который я нарисовал, состоит в том, что только B находится на расстоянии Rr от A. Все другие пункты будут> Rr далеко от A. Чего мне не хватает?
dbostream
@dbostream Вы ничего не упускаете. Если два круга уже соприкасаются, то реального столкновения обнаружить невозможно : столкновение происходит в B, и k=0. Теперь, если вы хотите разрешения коллизий , я не рассмотрел это в своем ответе, потому что это потребовало бы знания о физических свойствах объектов. Что должно произойти? Должен ли внутренний круг подпрыгивать внутри? Или катиться? Развертка?
Сэм Хоцевар
Я хочу, чтобы маленький круг начал скользить по дуге большого круга. Так что, если я не ошибаюсь, я хочу, чтобы точка столкновения на дуге большого круга, чтобы я мог использовать его нормаль, чтобы обновить скорость.
dbostream
@dbostream, если движение должно быть ограничено таким образом, то я предлагаю вам как можно скорее следовать этому ограничению: если скорость равна V, продвигать внутренний круг V*tпо окружности R-rкруга. Это означает вращение угловых V*t/(R-r)радианов вокруг точки A. И вектор скорости можно вращать таким же образом. Не нужно знать нормаль (которая всегда ориентирована на центр круга) или обновлять скорость любым другим способом.
Сэм Хоцевар
Мне все еще нужно переместить маленький круг до точки столкновения перед вращением. И когда я пытался вращать позицию, используя матрицу вращения, новая позиция была не совсем (но почти) Rr от центра большого круга, но этой крошечной разницы было достаточно, чтобы мой тест на столкновение провалился при следующем запуске. Нужно ли поворачивать позицию, чтобы найти новую, нельзя ли использовать векторные операции, как вы, если что-то сталкивается с прямой стеной?
dbostream
4

Скажем, большой круг - это круг А, а маленький круг - это круг Б.

Проверьте, находится ли B внутри A:

distance = sqrt((B.x - A.x)^2 + (B.y - A.y)^2))
if(distance > A.Radius + B.Radius) { // B is outside A }

Если в кадре n-1B было внутри A, а в кадре nB - вне A, и время между кадрами не было слишком большим (иначе B не двигалось слишком быстро), мы можем приблизить точку столкновения, просто найдя декартовы координаты B относительно к:

collision.X = B.X - A.X;
collision.Y = B.Y - A.Y;

Затем мы можем преобразовать эти точки в угол:

collision.Normalize(); //not 100% certain if this step is necessary     
radians = atan2(collision.Y, collision.X)

Если вы хотите более точно узнать, что tB находится за пределами A, то в первый раз вы можете выполнять пересечение окружности луча в каждом кадре, а затем сравнивать, если расстояние от B до точки столкновения больше, чем расстояние B, если оно текущая скорость. Если это так, вы можете рассчитать точное время столкновения.

Рой Т.
источник
Спасибо, но действительно ли правильно снимать луч из центра маленького круга при выполнении этого теста на пересечение? Разве мы не закончим со сценарием в середине этой картины ? Я имею в виду, что первая точка на дуге малого круга, которая сталкивается с большим кругом, не обязательно совпадает с точкой на дуге в направлении скорости. Я думаю, что мне нужно что-то вроде нижнего сценария картины, с которой я связан. Я добавил новую картинку в первый пост, показывающий пример того, что мне нужно.
dbostream
Хм, я полагаю, что сценарий возможен. Может быть, проверить с новым кругом C, который имеет B.Radius + максимальное движение B в этом кадре, проверить, сталкивается ли это с A, и затем потренировать точку на C, которая находится дальше от A. (Не пробовал это кстати)
Рой Т.
3
Используя меньше слов: if (distance (A, B))> (Ra-Rb) происходит столкновение, и вы просто перемещаете маленький круг, чтобы получить расстояние, равное Ra-Rb. В противном случае вы перемещаете маленький круг нормально. Кстати, @dbostream вы используете что-то похожее на упрощенную форму спекулятивных контактов, попробуйте найти это.
Darkwings
@Darkwings +1 ты абсолютно прав, и это звучит намного проще!
Рой Т.
Звучит просто, потому что я снял всю необходимую базовую геометрию. Вместо того, чтобы называть это «столкновением», вы могли бы назвать его связным, поскольку это то, чем оно является на самом деле: свободный вектор АВ, связанный с (0,0). Как только вы нормализуете его, вы получите как уравнение для параллели к AB пучку прямых, так и полезный единичный вектор. Затем вы можете умножить этот единичный вектор на любое расстояние D и добавить вновь найденные параметры в A, найдя нужную вам точку столкновения: C (Ax + Dx, Ay + Dy). Теперь это звучит сложнее, но это то же самое: P
Darkwings
0

Пусть (Xa, Ya) позиция большого круга и его радиус R, и (Xb, Yb) положение меньшего круга и его радиус r.

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

DistanceTest = sqrt(((Xa - Xb) ^ 2) + ((Ya - Yb) ^ 2)) >= (R - r)

Чтобы выяснить положение столкновения, найдите точный момент времени, когда круги сталкиваются, используя двоичный поиск, но с фиксированным числом шагов. В зависимости от того, как устроена ваша игра, вы можете оптимизировать эту часть кода (я предоставил это решение независимо от того, как ведет себя маленький шарик. Если он имеет постоянное ускорение или постоянную скорость, эту часть кода можно оптимизировать и заменить на простую формулу).

left = 0 //the frame with no collision
right = 1 //the frame after collision
steps = 8 //or some other value, depending on how accurate you want it to be
while (steps > 0)
    checktime = left + (right - left) / 2
    if DistanceTest(checktime) is inside circle //if at the moment in the middle of the interval [left;right] the small circle is still inside the bigger one
        then left = checktime //the collision is after this moment of time
        else right = checktime //the collision is before
    steps -= 1
finaltime = left + (right - left) / 2 // the moment of time will be somewhere in between, so we take the moment in the middle of interval to have a small overall error

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

CollisionX = (Xb - Xa)*R/(R-r) + Xa
CollisionY = (Yb - Ya)*R/(R-r) + Ya
влад
источник
0

Я реализовал демонстрацию отскока мяча по кругу на jsfiddle, используя алгоритм, описанный Сэмом Хочеваром :

http://jsfiddle.net/klenwell/3ZdXf/

Вот javascript, который определяет точку контакта:

find_contact_point: function(world, ball) {
    // see https://gamedev.stackexchange.com/a/29658
    var A = world.point();
    var B = ball.point().subtract(ball.velocity());
    var C = ball.point();
    var R = world.r;
    var r = ball.r;

    var AB = B.subtract(A);
    var BC = C.subtract(B);
    var AB_len = AB.get_length();
    var BC_len = BC.get_length();

    var b = AB.dot(BC) / Math.pow(BC_len, 2) * -1;
    var c = (Math.pow(AB_len, 2) - Math.pow(R - r, 2)) / Math.pow(BC_len, 2);
    var d = b * b - c;
    var k = b - Math.sqrt(d);

    if ( k < 0 ) {
        k = b + Math.sqrt(d);
    }

    var BD = C.subtract(B);
    var BD_len = BC_len * k;
    BD.set_length(BD_len);

    var D = B.add(BD);
    return D;
}
klenwell
источник