Коло зіткнення всередині кола


9

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

Основний приклад

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

Що я хотів би зробити, це перед тим, як рухати мале коло, я передбачую його наступне положення, і якщо воно знаходиться поза, я знаходжу час зіткнення між t = 0 і t = 1 (t = 1 повний крок часу). Якщо у мене час зіткнення t, то я просто переміщу мале коло під час t замість повного кроку часу. Але знову ж таки, проблема полягає в тому, що я не знаю, як виявити на той момент зіткнення, коли мова йде про два кола і одне буває всередині іншого.

Редагувати:

Приклад точки зіткнення (зелений) Я хочу знайти. Можливо, картина трохи відключена, але ви отримуєте ідею.

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

Відповіді:


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. Тепер, якщо ви хочете вирішити зіткнення , я не висвітлював це у своїй відповіді, оскільки це вимагало б знань про фізичні властивості об'єктів. Що має статися? Чи повинен внутрішній коло підстрибувати всередині? Або рулон? Підмітати?
sam hocevar

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

@dbostream, якщо рух слід обмежувати таким чином, то я пропоную вам дотримуватися цього обмеження якомога швидше: якщо швидкість V, зробіть внутрішнє коло вперед V*tпо колу R-rкола. Це означає обертання кутових V*t/(R-r)радіанів навколо точки A. І вектор швидкості можна обертати таким же чином. Не потрібно знати нормальну (яка так чи інакше завжди орієнтована на центр кола) або оновлювати швидкість будь-яким іншим способом.
sam hocevar

Мені ще потрібно перенести мале коло до точки зіткнення перед обертанням. І коли я спробував повернути положення за допомогою матриці обертання, нове положення не було точно (але майже) на відстані від центру великого кола, але цієї невеликої різниці було достатньо, щоб мій тест зіткнення провалився в наступному пробігу. Чи потрібно мені обертати положення, щоб знайти нове, чи не можна використовувати векторні операції, як ви можете, якщо щось стикається з прямою стіною?
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 до A:

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. (Не пробував цього BTW)
Рой Т.

3
Використання менше слів: якщо (відстань (A, B))> (Ra-Rb) трапляється зіткнення, і ви просто переміщаєте невелике коло, щоб отримати відстань, рівну Ra-Rb. Інакше ти нормально рухаєш маленьке коло. До речі, @dbostream ви використовуєте щось подібне до спрощеної форми Спекулятивних контактів, спробуйте знайти це.
Darkwings

@Darkwings +1 Ви абсолютно абсолютно праві, і це звучить набагато простіше!
Рой Т.

Це звучить просто, тому що я позбавив всю необхідну геометрію основи. Замість того, щоб називати це "зіткненням", ви могли б назвати його обмеженимAB, оскільки це власне: вільний вектор АВ пов'язаний з (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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.