Зіткнення куля в кулю - виявлення та обробка


266

За допомогою спільноти Stack Overflow я написав досить базовий, але веселий фізичний симулятор.

alt текст

Ви натискаєте та перетягуєте мишу, щоб запустити кульку. Він відскочить і врешті-решт зупиниться на «підлозі».

Наступна моя велика особливість, яку я хочу додати, - це зіткнення м'яча на кулю. Рух кулі розбивається на вектор оси та y швидкості. У мене гравітація (невелике зменшення y вектора на кожен крок), у мене тертя (невелике зменшення обох векторів при кожному зіткненні зі стінкою). Кулі чесно рухаються навколо напрочуд реалістичним способом.

Я думаю, моє запитання має дві частини:

  1. Який найкращий спосіб виявити зіткнення кульки в кулю?
    Чи просто у мене є петля O (n ^ 2), яка переходить над кожною кулькою і перевіряє кожну іншу кулю, щоб побачити, чи перекривається її радіус?
  2. Які рівняння я використовую для обробки кулі до зіткнення кулі? Фізика 101
    Як це впливає на швидкість руху двох кульок х / у? У якому отриманому напрямку обидва кулі відхиляються? Як застосувати це до кожного кульки?

alt текст

Впоратися з виявленням зіткнень "стінок" та отриманими в результаті цього векторними змінами було легко, але я бачу більше ускладнень при зіткненнях з кулькою. Зі стінами я просто повинен був взяти мінус відповідного вектора x або y, і він пішов би в правильному напрямку. З кульками я не думаю, що це так.

Деякі швидкі роз'яснення: для простоти я все в порядку з ідеально еластичним зіткненням, також всі мої кулі мають тепер однакову масу, але я можу змінити це в майбутньому.


Редагувати: Ресурси, які я вважаю корисними

Фізика 2d кульки з векторами: 2-мірними зіткненнями без Trigonometry.pdf
прикладу виявлення зіткнення 2d Болла: Додавання виявлення зіткнень


Успіху!

У мене виявлення та реакція кульових зіткнень чудово працюють!

Відповідний код:

Виявлення зіткнення:

for (int i = 0; i < ballCount; i++)  
{  
    for (int j = i + 1; j < ballCount; j++)  
    {  
        if (balls[i].colliding(balls[j]))  
        {
            balls[i].resolveCollision(balls[j]);
        }
    }
}

Це дозволить перевірити наявність зіткнень між кожним м'ячем, але пропустити зайві чеки (якщо вам доведеться перевірити, чи м'яч 1 стикається з м'ячем 2, то вам не потрібно перевіряти, чи куля 2 стикається з м'ячем 1. Також він пропускає перевірку на зіткнення з самим собою ).

Потім у моєму бальному класі я використовую свої методи зіткнення () та вирішення колізій ():

public boolean colliding(Ball ball)
{
    float xd = position.getX() - ball.position.getX();
    float yd = position.getY() - ball.position.getY();

    float sumRadius = getRadius() + ball.getRadius();
    float sqrRadius = sumRadius * sumRadius;

    float distSqr = (xd * xd) + (yd * yd);

    if (distSqr <= sqrRadius)
    {
        return true;
    }

    return false;
}

public void resolveCollision(Ball ball)
{
    // get the mtd
    Vector2d delta = (position.subtract(ball.position));
    float d = delta.getLength();
    // minimum translation distance to push balls apart after intersecting
    Vector2d mtd = delta.multiply(((getRadius() + ball.getRadius())-d)/d); 


    // resolve intersection --
    // inverse mass quantities
    float im1 = 1 / getMass(); 
    float im2 = 1 / ball.getMass();

    // push-pull them apart based off their mass
    position = position.add(mtd.multiply(im1 / (im1 + im2)));
    ball.position = ball.position.subtract(mtd.multiply(im2 / (im1 + im2)));

    // impact speed
    Vector2d v = (this.velocity.subtract(ball.velocity));
    float vn = v.dot(mtd.normalize());

    // sphere intersecting but moving away from each other already
    if (vn > 0.0f) return;

    // collision impulse
    float i = (-(1.0f + Constants.restitution) * vn) / (im1 + im2);
    Vector2d impulse = mtd.normalize().multiply(i);

    // change in momentum
    this.velocity = this.velocity.add(impulse.multiply(im1));
    ball.velocity = ball.velocity.subtract(impulse.multiply(im2));

}

Джерело: Повне джерело для кульового колайдера.

Якщо у когось є якісь пропозиції щодо вдосконалення цього базового фізичного симулятора, дайте мені знати! Одне, що мені ще потрібно додати, це кутовий імпульс, тому кулі будуть котитися більш реалістично. Будь-які інші пропозиції? Залишити коментар!


16
Я не думаю, що цей алгоритм є досить хорошим, тому що якщо ваші кулі рухаються занадто швидко (наприклад: швидше 2 * радіус на кадр, одна куля може пройти через іншу кулю без будь-яких зіткнень.
Benji Mizrahi,

@Simulcal ви могли б знову завантажити свій вихідний код (всі посилання на fileropper.com здаються порушеними). Ви також можете встановити файл PDF, який ви отримали від [geocities.com/vobarian/2dcollisions/2dcollisions.pdf], оскільки
геогради

1
Ось посилання на останню версію BallBounce, над якою я працював: dl.dropbox.com/u/638285/ballbounce.rar
mmcdole

@До всіх, хто сприяв: Чи можете ви пролити трохи світла для перетворення цього двигуна в 3D. Як цей чудовий двигун також може працювати в Java3D.
статична недійсна головна

2
Рядок Vector2d impulse = mtd.multiply(i);повинен бути i * нормалізованим вектором mtd. Щось на кшталт:Vector2d impulse = mtd.normalize().multiply(i);
klenwell

Відповіді:


117

Щоб виявити, чи стикаються дві кулі, просто перевірте, чи відстань між їх центрами менше, ніж у два рази перевищує радіус. Щоб здійснити ідеально пружне зіткнення між кулями, потрібно лише турбуватися про складову швидкості, яка знаходиться в напрямку зіткнення. Інший компонент (дотичний до зіткнення) залишиться однаковим для обох кульок. Ви можете отримати компоненти зіткнення, створивши одиничний вектор, що вказує у напрямку від однієї кулі до іншої, потім взявши крапковий добуток із векторами швидкості кульок. Потім ви можете підключити ці компоненти до 1D ідеально еластичного рівняння зіткнення.

У Вікіпедії є досить хороший підсумок всього процесу . Для кульок будь-якої маси нові швидкості можна обчислити, використовуючи рівняння (де v1 і v2 - швидкості після зіткнення, а u1, u2 - від раніше):

v_ {1} = \ frac {u_ {1} (m_ {1} -m_ {2}) + 2m_ {2} u_ {2}} {m_ {1} + m_ {2}}

v_ {2} = \ frac {u_ {2} (m_ {2} -m_ {1}) + 2m_ {1} u_ {1}} {m_ {1} + m_ {2}}

Якщо кульки мають однакову масу, то швидкості просто перемикаються. Ось я написав якийсь код, який робить щось подібне:

void Simulation::collide(Storage::Iterator a, Storage::Iterator b)
{
    // Check whether there actually was a collision
    if (a == b)
        return;

    Vector collision = a.position() - b.position();
    double distance = collision.length();
    if (distance == 0.0) {              // hack to avoid div by zero
        collision = Vector(1.0, 0.0);
        distance = 1.0;
    }
    if (distance > 1.0)
        return;

    // Get the components of the velocity vectors which are parallel to the collision.
    // The perpendicular component remains the same for both fish
    collision = collision / distance;
    double aci = a.velocity().dot(collision);
    double bci = b.velocity().dot(collision);

    // Solve for the new velocities using the 1-dimensional elastic collision equations.
    // Turns out it's really simple when the masses are the same.
    double acf = bci;
    double bcf = aci;

    // Replace the collision velocity components with the new ones
    a.velocity() += (acf - aci) * collision;
    b.velocity() += (bcf - bci) * collision;
}

Що стосується ефективності, то Райан Фокс має рацію, вам слід розглянути поділ регіону на секції, а потім зробити виявлення зіткнень у межах кожної секції. Майте на увазі, що кулі можуть стикатися з іншими кульками на межах розділу, тому це може зробити ваш код набагато складнішим. Ефективність, мабуть, не має значення, поки у вас не буде декількох сотень балів. Для отримання бонусних балів ви можете запустити кожен розділ на іншому ядрі або розділити обробку зіткнень у межах кожного розділу.


2
Скажімо, маса двох куль не однакова. Як це впливає на зміну вектора між кулями?
mmcdole

3
Минув час з 12 класу, але я думаю, що вони отримують відношення імпульсу, що відповідає співвідношенню мас.
Райан Фокс

6
@ Джей, лише щоб зазначити .. що одне додане вами зображення рівняння стосується 1-мірного зіткнення, а не двовимірного.
mmcdole

@simucal. неправда ... u і v є векторами цього рівняння. Тобто вони мають x, y (і z) компоненти.
Ендрю Роллінгс

2
@Simucal, ви праві, вони є для одновимірного випадку. Для отримання додаткових розмірів просто використовуйте компоненти швидкості, які відповідають колізії (aci, bci в коді). Інші компоненти є ортогональними щодо зіткнення і не змінюватимуться, тому вам не потрібно переживати про них.
Джей Конрод

48

Ну, років тому я зробив програму, як ви представили тут.
Є одна прихована проблема (або багато, залежить від точки зору):

  • Якщо швидкість кульки занадто висока, ви можете пропустити зіткнення.

А також, майже в 100% випадків ваші нові швидкості будуть неправильними. Ну не швидкості , а позиції . Ви повинні обчислити нові швидкості точно в потрібному місці. Інакше ви просто переміщуєте кульки на якусь невелику кількість помилок, яка доступна з попереднього дискретного кроку.

Рішення очевидно: вам потрібно розділити часовий крок таким чином, що спочатку ви зміщуєтесь у потрібне місце, потім стикаєтеся, а потім зміщуєте на інший час, який у вас є.


Якщо положення зсуву увімкнено timeframelength*speed/2, то позиції будуть статистично фіксованими.
Накілон

@Nakilon: ні, це допомагає лише в деяких випадках, але загалом зіткнення можна пропустити. І ймовірність пропустити зіткнення зростає з розміром довжини рамки часу. До речі, здається, що Алеф продемонстрував правильне рішення (я просто прокинув його, хоча).
avp

1
@avp, я не про те, якщо швидкість м'яча занадто висока, ви можете пропустити зіткнення. , але щодо ваших нових посад буде неправильним . Через зіткнення виявляють трохи пізніше, ніж вони дійсно зіткнулися, якщо субстрат timeframelength*speed/2з цієї позиції точність збільшиться вдвічі.
Накілон

20

Для вирішення цієї проблеми слід використовувати розділення простору.

Читайте про розділення бінарного простору та квадри


4
Замість того, щоб розділити простір, хіба алгоритм розгортки та обрізки не працюватиме краще? кулі рухаються швидко, тому будь-яку секціювання доведеться часто оновлювати, що несе накладні витрати. Зачистка та підрізання могли знайти всі стикаються пари в O (n log n), без перехідних структур даних. Ось хороший підручник з основ
HugoRune

13

Як пояснення до пропозиції Райана Фокса розділити екран на регіони і лише перевірити на зіткнення в межах регіонів ...

наприклад, розділіть ігрову зону вгору на сітку квадратів (яка буде довільно сказати, що мають довжину 1 одиниця на сторону), і перевірте наявність зіткнень у межах кожного квадрату сітки.

Це абсолютно правильне рішення. Єдина проблема з цим (як вказував інший плакат) полягає в тому, що колізії через межі є проблемою.

Рішення цього полягає в накладанні другої сітки з вертикальним і горизонтальним зміщенням на одиницю 0,5 на першу.

Тоді будь-які зіткнення, які були б через межі в першій сітці (а значить, не були виявлені), будуть знаходитися в межах сітки у другій сітці. Поки ви відстежуєте зіткнення, з якими ви вже впоралися (оскільки, ймовірно, буде певне перекриття), вам не доведеться турбуватися про обробку крайових справ. Усі зіткнення відбуватимуться в межах сітки на одній із сіток.


+1 для більш точного рішення та протидії боягузливому керованому судноводію
Стівен А. Лоу

1
це хороша ідея. Я зробив це один раз, і я перевірив поточну клітинку та всі сусідні клітини, але ваш метод є більш ефективним. Ще один спосіб, про який я думав, - це перевірити поточну комірку, а потім перевірити, чи перетинається вона з поточними межами комірок, і якщо так, перевірити об’єкти в ТОМУ сусідній комірці.
LoveMeSomeCode

10

Хороший спосіб зменшити кількість перевірок на зіткнення - розділити екран на різні секції. Потім ви лише порівнюєте кожну кулю з кулями в тому ж розділі.


5
Виправлення: вам потрібно перевірити на зіткнення з тими ж І сусідніми ділянками
rint

7

Я бачу тут одну оптимізацію.

Хоча я згоден, що кулі потрапляють, коли відстань є сумою їх радіусів, але насправді ніколи не слід обчислювати цю відстань! Швидше обчисліть його квадрат і працюйте з ним таким чином. Для цієї дорогої операції з квадратним коренем немає причин.

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

Що стосується O (n ^ 2), все, що ви можете зробити, - це мінімізувати витрати на відхилення тих, які пропускають:

1) Куля, яка не рухається, нічого не може вдарити. Якщо на підлозі лежить достатня кількість кульок, це може врятувати багато випробувань. (Зверніть увагу, що ви все ж повинні перевірити, чи щось потрапило в нерухомий куля.)

2) Щось, що, можливо, варто зробити: Розділіть екран на кілька зон, але лінії повинні бути нечіткими - кулі на краю зони перераховані як такі, що є у всіх відповідних (може бути 4) зонах. Я використовував би сітку 4x4, зберігав зони як біти. Якщо І з зон двох кульових зон повертає нуль, кінець тесту.

3) Як я вже згадував, не робіть квадратний корінь.


Дякуємо за інформацію про квадратний наконечник. Не знав про його дорогу природу в порівнянні з площею.
mmcdole

Ще однією оптимізацією було б знайти кулі, які ніде не знаходяться поблизу інших куль. Це надійно працюватиме лише за умови обмеження швидкостей кульок.
Бред Гілберт

1
Я не згоден у пошуках ізольованих куль. Це так само дорого, як і виявлення зіткнення. Щоб покращити речі, вам потрібно щось менше, ніж O (n) для відповідного балу.
Лорен Печтел

7

Я знайшов чудову сторінку з інформацією про виявлення та реагування на зіткнення у 2D.

http://www.metanetsoftware.com/technique.html

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

Редагувати: оновлене посилання


3

У вас є два простих способи зробити це. Джей висвітлив точний спосіб перевірки з центру кулі.

Найпростіший спосіб - використовувати прямокутний обмежувальний ящик, встановити розмір коробки на 80% від розміру кулі, і ви будете імітувати зіткнення досить добре.

Додайте метод до класу балів:

public Rectangle getBoundingRect()
{
   int ballHeight = (int)Ball.Height * 0.80f;
   int ballWidth = (int)Ball.Width * 0.80f;
   int x = Ball.X - ballWidth / 2;
   int y = Ball.Y - ballHeight / 2;

   return new Rectangle(x,y,ballHeight,ballWidth);
}

Потім у своєму циклі:

// Checks every ball against every other ball. 
// For best results, split it into quadrants like Ryan suggested. 
// I didn't do that for simplicity here.
for (int i = 0; i < balls.count; i++)
{
    Rectangle r1 = balls[i].getBoundingRect();

    for (int k = 0; k < balls.count; k++)
    {

        if (balls[i] != balls[k])
        {
            Rectangle r2 = balls[k].getBoundingRect();

            if (r1.Intersects(r2))
            {
                 // balls[i] collided with balls[k]
            }
        }
    }
}

1
Це призвело б до того, щоб кулі переходили одна на одну на 20% при горизонтальних та вертикальних зіткненнях. Можна також використовувати круглі обмежувальні коробки, оскільки різниця в ефективності незначна. Також (x-width)/2має бути x-width/2.
Маркус Джардеро

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

Ви можете зробити прямокутне обмежувальне поле, тоді, якщо він має удар, перевірте кругову рамку для обмеження.
Бред Гілберт

1
@ Джонатан Голланд, ваш внутрішній цикл повинен бути для (int k = i + 1; ...) Це дозволить позбутися всіх зайвих перевірок. (тобто перевірка зіткненням себе і перевірка кулі зіткнення1 з кулькою2, а потім кулька2 з кулькою1).
mmcdole

4
Насправді квадратний обмежувальний ящик, ймовірно, буде гіршим за продуктивність, ніж круговий обмежувальний ящик (якщо припустити, що ви оптимізували квадратний корінь далеко)
Ponkadoodle

3

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

Математика додавання / різниці набагато швидша для обмежувальної коробки, ніж усі тригери для радіусу, і в більшості разів тест обмежувальної коробки відхиляє можливість зіткнення. Але якщо потім повторно протестувати за допомогою тригера, ви отримуєте точні результати, яких ви шукаєте.

Так, це два тести, але в цілому це буде швидше.


6
Вам не потрібен триг. bool is_overlapping(int x1, int y1, int r1, int x2, int y2, int r2) { return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)<(r1+r2)*(r1+r2); }
Ponkadoodle


2

Я реалізував цей код у JavaScript за допомогою елемента HTML Canvas, і він створив чудові симуляції зі швидкістю 60 кадрів в секунду. Я розпочав моделювання з колекції з десятка куль у випадкових положеннях та швидкостях. Я виявив, що на більших швидкостях оглядовий зіткнення між маленькою кулею та значно більшою змусив маленьку кульку здатися STICK до краю великої кулі та перемістився до 90 градусів навколо великої кулі перед відокремленням. (Цікаво, чи хтось ще спостерігав таку поведінку.)

Деякі реєстрації обчислень показали, що мінімальна відстань перекладу в цих випадках була недостатньо великою, щоб запобігти зіткненню тих же кульок на наступному кроці. Я зробив кілька експериментів і виявив, що міг вирішити цю проблему шляхом збільшення МТД на основі відносних швидкостей:

dot_velocity = ball_1.velocity.dot(ball_2.velocity);
mtd_factor = 1. + 0.5 * Math.abs(dot_velocity * Math.sin(collision_angle));
mtd.multplyScalar(mtd_factor);

Я переконався, що до і після цього виправлення загальна кінетична енергія зберігалася для кожного зіткнення. Значення 0,5 у mtd_factor було приблизно мінімальним значенням, яке, як виявлено, завжди спричиняє розділення кульок після зіткнення.

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


1
sin (..) - не дешева функція
PaulHK
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.