Advertisement
  1. Code
  2. Coding Fundamentals
  3. Game Development

Як створити користувальницький 2D-фізичний движок: Основи та імпульсну роздільну здатність

Scroll to top
Read Time: 20 min
This post is part of a series called How to Create a Custom Physics Engine.
How to Create a Custom 2D Physics Engine: The Core Engine

() translation by (you can also view the original English article)

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

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

До кінця цього підручника у наступних розділах будуть покриті, в двох вимірах:

  •  Просте виявлення зіткнень
  • Простий колектор покоління
  •  Імпульсна роздільна здатність

Ось короткий демо:

Примітка: хоча цей підручник написаний на C++, ви повинні бути в змозі використовувати ті ж методи і концепції практично в будь-якому середовищі розробки.


Передумови

Ця стаття включає досить велику кількість математики і геометрії, і в набагато меншому ступені фактичного кодування. Пару передумов для цієї статті є:

  • Базове розуміння простої векторної математики
  • Уміння виконувати алгебраїчні математика

Виявлення Зіткнень

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

Осі Вирівняні Обмежують Прямокутників

 Осі вирівняні прямокутника (ААВВ) - це вікно, яке має чотири осі поєднана з системою координат, в якій він проживає.  Це означає, що це поле, яке не може обертатися, і завжди квадрат на 90 градусів (зазвичай вирівнюється з екрану).  В цілому це називається "прямокутник", тому що AABBs використовуються для пов'язані інші, більш складні форми.

An example AABBAn example AABBAn example AABB

Приклад ААВВ.

 ААВВ складної форми можна використовувати як простий тест, щоб побачити, якщо більш складні форми всередині AABBs може бути пересічними.  Однак у випадку більшості ігор ААВВ використовується в якості фундаментальної форми, і насправді не пов'язані що-небудь інше. Структура ААВВ важливо.  Є кілька різних способів, щоб представляти ААВВ, однак це моє улюблене:

1
struct AABB
2
{
3
  Vec2 min;
4
  Vec2 max;
5
};

Ця форма дозволяє ААВВ бути представлені дві точки.  Мінімальна точка являє собою нижню межі X і осі Y, і Макс являє собою верхні межі - іншими словами, вони являють собою верхній лівий і нижній правий кути.  Для того, щоб сказати, чи є два ААВВ форми пересічних необхідно мати базове розуміння поділу Теорема осі (СБ).

 Ось швидкий тест, взятий з реального часу виявлення зіткнень Крістер Еріксон, яка дозволяє використовувати зб.:

1
bool AABBvsAABB( AABB a, AABB b )
2
{
3
  // Exit with no intersection if found separated along an axis

4
  if(a.max.x < b.min.x or a.min.x > b.max.x) return false
5
  if(a.max.y < b.min.y or a.min.y > b.max.y) return false
6
7
  // No separating axis found, therefor there is at least one overlapping axis

8
  return true
9
}

Круги

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

1
struct Circle
2
{
3
  float radius
4
  Vec position
5
};

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

 Важливим оптимізації, щоб зробити тут, це позбутися від необхідності використання оператора квадратного кореня:

1
float Distance( Vec2 a, Vec2 b )
2
{
3
  return sqrt( (a.x - b.x)^2 + (a.y - b.y)^2 )
4
}
5
6
bool CirclevsCircleUnoptimized( Circle a, Circle b )
7
{
8
  float r = a.radius + b.radius
9
  return r < Distance( a.position, b.position )
10
}
11
12
bool CirclevsCircleOptimized( Circle a, Circle b )
13
{
14
  float r = a.radius + b.radius
15
  r *= r
16
  return r < (a.x + b.x)^2 + (a.y + b.y)^2
17
}

В цілому розмноження-це набагато дешевше, ніж операція взяття квадратного кореня значення.


Дозвіл Імпульс

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

 В цілому об'єкт у фізичний движок має три ступені свободи (у двох вимірах): рух в площині XY і обертання.  У цій статті ми неявно обмежити обертання і використовувати тільки AABBs та кола, так що єдина ступінь свободи, ми дійсно повинні розглянути рух вздовж площини XY.

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

Simple example of what impulse resolution can achieveSimple example of what impulse resolution can achieveSimple example of what impulse resolution can achieve

Простий приклад того, що дозвіл імпульс може досягти.

 Для досягнення таких ефектів і слідкуйте за поряд з природною інтуїцією, як ведуть себе об'єкти ми будемо використовувати тверді тіла і трохи математики.  Тверде тіло-це просто форма, визначених користувачем (тобто вами, забудовника), який неявно певними, щоб не деформуватися.  Обидва AABBs і кола в цій статті, недеформіруемой, і завжди буде або ААВВ або коло. Ніякого стиснення або розтягування допускається.

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

 Наші Об'єкти Зіткнувся - Тепер Що?

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

  • Зіткнення нормально
  • Глибина проникнення

 Для того, щоб застосувати імпульс до об'єктів, але і переміщати їх один від одного, ми повинні знати, в якому напрямку штовхати їх і на скільки. Нормальне зіткнення напрямок, в якому імпульс буде застосовуватися.  Глибина проникнення (разом з деякими іншими речами) визначити, як великі імпульсу буде використовуватися.  Це означає, що єдина цінність, яку потрібно вирішити для масштабів нашого імпульсу.

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

 Рівняння 1

\[ В^{АВ} = V і^Б - В^А \] зауважимо, що для того, щоб створити вектор з позиції a в позицію B, ви повинні зробити: кінцева точка - Вихідна позиція.  \(^{АВ}\) - відносна швидкість з А в Б. Це рівняння має бути виражена в термінах зіткнення нормальний \(н\) - тобто, ми хотіли б знати відносну швидкість від А до Б уздовж лінії зіткнення нормального напрямки:

Рівняння 2

 \[ В^{АВ} \cDOT на Н = (^Б - В^А) \cDOT на п \]

Ми зараз використовуємо скалярний добуток. Скалярний добуток простих; це сума по компонентно продуктів:

 Рівняння 3

 \[ В_1 = завжди \begin{bmatrix}x_1 місця \\y_1\кінець{bmatrix}, В_2 = завжди \begin{bmatrix}x_2 \\y_2\кінець{bmatrix} \\ В_1 \cDOT на В_2 = x_1 місця * x_2 + y_2 * y_2 \]

Наступний крок-ввести так званий коефіцієнт реституції. Реституція-це термін, який означає еластичність, або bounciness. Кожен об'єкт в свій фізичний движок буде реституції представляється як десяткове значення.  Проте тільки одне десяткове значення буде використовуватися при розрахунку імпульсу.

 Щоб вирішити, що реституція використовувати (позначається \Е\) для Епсилон), ви повинні завжди використовувати найнижчою реституції, залучені в конфлікт, для інтуїтивного результати:

1
// Given two objects A and B

2
e = min( A.restitution, B.restitution )

 Після того як \Е\) є придбаним, ми можемо розмістити його в нашому вирішуючи рівняння для величини імпульсу.

Закон Ньютона про реституцію говориться наступне:

Рівняння 4

 \[В' = Е * \у]

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

Рівняння 5

 \[ В^{АВ} \cDOT на Н = -Е * ^Б - В^А) \cDOT на п \]

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

Досі так добре.  Тепер ми повинні бути в змозі висловити ці швидкості в той час як під впливом імпульсу. Ось просте рівняння для зміни вектора яким-імпульсу скалярного \(\) уздовж певної \напрям(Н\):

Рівняння 6

 \[ У' = у + J В * Н \]

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

Є трохи більше роботи, щоб бути зроблено тут. Формально, імпульс визначається як зміна імпульсу. Імпульс-це маса * швидкість.  Знаючи це, ми можемо уявити імпульс як формально визначається так:

Рівняння 7

 \[ Імпульс = маса * швидкість \\ швидкість = \фрац{імпульс}{маса} \отже, у' = у + \фрац{Дж * Н}{маса}\]

Три крапки в маленький трикутник (\(\тому\)) можна читати Як "тому". Він використовується, щоб показати, що справа заздалегідь може бути використаний, щоб зробити висновок, що все, що далі вірно.

Хороший прогрес був досягнутий досі!  Однак ми повинні бути в змозі висловити імпульс через \(\) щодо двох різних об'єктів.  При зіткненні з об'єктом A і B, а зсуваються в протилежному напрямку Б:

Рівняння 8

 \[ В'^У = ^А + \фрац{Дж * Н}{мас^а} \ \ '^У = ^Б - \фрац{Дж * Н}{маса^Б} \]

 Ці два рівняння будуть штовхати від Б вздовж напрямку одиничного вектора \(н\) з імпульсним скаляр (величина \(н\)) \(\).

 Все, що зараз потрібно, це об'єднати рівняння 8 і 5. Наші отримане рівняння буде виглядати так:

Рівняння 9

 \ [ (^А - ^ + \фрац{Дж * Н}{мас^а} + \фрац{Дж * Н}{маса^б}) * н = -е * ^Б - В^А) \cDOT на п \\ \Тому \ \ (^А - ^ + \фрац{Дж * Н}{мас^а} + \фрац{Дж * Н}{маса^Б}) * П + Е * ^Б - В^А) \cDOT на н = 0 \]

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

Рівняння 10

 \[ (П^Б - В^А) \cDOT на н + з * (\фрац{Дж * Н}{мас^а} + \фрац{Дж * Н}{маса^б}) * н + е * ^Б - В^А) \cDOT на н = 0 \\ \тому \\ (1 + е) (В^Б - В^А) \cDOT на н) + з * (\фрац{Дж * Н}{мас^а} + \фрац{Дж * Н}{маса^Б}) * П = 0 \\ \тому \\ Дж = \фрац{-(1 + е) (В^Б - В^А) \cDOT на Н)}{\ГРП{1}{мас^а} + \ГРП{1}{маса^Б}} \]

 Ух!  Це було трохи математики!  Це зараз, хоча у всьому.  Важливо зауважити, що в остаточному варіанті рівняння 10 ми маємо \(\) зліва (наші масштаби) і все, що праворуч-це все відомо.  Це означає, що ми можемо написати кілька рядків коду, щоб вирішити для нашого імпульсу скалярного \(\).  І хлопчик-це код набагато більш читабельним, ніж математичної нотації!

1
void ResolveCollision( Object A, Object B )
2
{
3
  // Calculate relative velocity

4
  Vec2 rv = B.velocity - A.velocity
5
6
  // Calculate relative velocity in terms of the normal direction

7
  float velAlongNormal = DotProduct( rv, normal )
8
9
  // Do not resolve if velocities are separating

10
  if(velAlongNormal > 0)
11
    return;
12
13
  // Calculate restitution

14
  float e = min( A.restitution, B.restitution)
15
16
  // Calculate impulse scalar

17
  float j = -(1 + e) * velAlongNormal
18
  j /= 1 / A.mass + 1 / B.mass
19
20
  // Apply impulse

21
  Vec2 impulse = j * normal
22
  A.velocity -= 1 / A.mass * impulse
23
  B.velocity += 1 / B.mass * impulse
24
}

 Є декілька ключових речей, щоб відзначити в наведеному вище прикладі коду.  Першим ділом перевірка у рядку 10, якщо(VelAlongNormal > 0).  Ця перевірка дуже важлива, вона гарантує, що ви тільки дозволяти колізію, якщо об'єкти рухаються назустріч один одному.

Two objects collide but velocity will separate them next frame Do not resolve this type of collisionTwo objects collide but velocity will separate them next frame Do not resolve this type of collisionTwo objects collide but velocity will separate them next frame Do not resolve this type of collision

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

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

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

1
A.inv_mass = 1 / A.mass
Багато фізики двигуни насправді не зберігати сирої маси.  Фізика двигуни часто магазин зворотна маси і один інверсний маси. Просто так вийшло, що більшість математичних участю мас у вигляді 1/маса.

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

 Для того щоб зробити це, ви могли б зробити:

1
float mass_sum = A.mass + B.mass
2
float ratio = A.mass / mass_sum
3
A.velocity -= ratio * impulse
4
5
ratio = B.mass / mass_sum
6
B.velocity += ratio * impulse

 Важливо розуміти, що приведений вище код еквівалентний ResolveCollision() приклад функції раніше. Як вже говорилося, зворотна мас вельми корисні в фізичний движок.

 Тонути Об'єктів

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

 Я пропоную використовувати нуль як нескінченну масу - Хоча, якщо ми спробуємо обчислити зворотну маса об'єкта з нуля у нас буде ділення на нуль. Обхідний шлях для цього це зробити наступне при обчисленні зворотних мас:

1
if(A.mass == 0)
2
  A.inv_mass = 0
3
else
4
  A.inv_mass = 1 / A.mass

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

Це тоне з-за плаваючою помилки точок.  Під час кожного розрахунку плаваючою точкою невеликий плаваючою помилки вводиться через апаратних. (Для отримання додаткової інформації, компанія Google [помилка плаваючою точкою IEEE754].)  З плином часу ця помилка накопичується в позиційну помилку, змушуючи об'єкти занурюватися в один одного.

 Для того, щоб виправити цю помилку вона повинна бути врахована.  Щоб виправити цю позиційну помилку я покажу вам метод, званий лінійної проекції. Лінійна проекція зменшує проникнення двох об'єктів на невеликий відсоток, і це відбувається після застосування імпульсу.  Позиційна корекція дуже проста: переміщення кожного об'єкта уздовж лінії зіткнення нормальний \(н\) у відсотках від глибини проникнення:

1
void PositionalCorrection( Object A, Object B )
2
{
3
  const float percent = 0.2 // usually 20% to 80%

4
  Vec2 correction = penetrationDepth / (A.inv_mass + B.inv_mass)) * percent * n
5
  A.position -= A.inv_mass * correction
6
  B.position += B.inv_mass * correction
7
}

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

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

1
void PositionalCorrection( Object A, Object B )
2
{
3
  const float percent = 0.2 // usually 20% to 80%

4
  const float slop = 0.01 // usually 0.01 to 0.1

5
  Vec2 correction = max( penetration - k_slop, 0.0f ) / (A.inv_mass + B.inv_mass)) * percent * n
6
  A.position -= A.inv_mass * correction
7
  B.position += B.inv_mass * correction
8
}

 Це дозволяє об'єктам проникнути дуже небагато без корекції положення в ногами.


 Простий Колектор Покоління

 Останні теми в даній статті, є простий колектор покоління.  Колектор в математичному плані-це щось вздовж ліній "колекцію очок, яка являє собою область в просторі".  Однак, коли я посилаюся на цей термін колектор я маю на увазі невеликий об'єкт, який містить інформацію про зіткнення між двома об'єктами.

 Ось типовий колектор установки:

1
struct Manifold
2
{
3
  Object *A;
4
  Object *B;
5
  float penetration;
6
  Vec2 normal;
7
};

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

 Коло кола проти

 Давайте почнемо з алгоритму найпростішої зіткнення: коло проти кола.  Цей тест в основному тривіальними. Чи Можете ви уявити, що в напрямку вирішення конфлікту буде?  Це вектор від точки А в точку Б. Це коло може бути отриманий шляхом вирахування установки Б У в.

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

 Ось повний приклад алгоритму колектор для генерації кола проти зіткнення колі:

1
bool CirclevsCircle( Manifold *m )
2
{
3
  // Setup a couple pointers to each object

4
  Object *A = m->A;
5
  Object *B = m->B;
6
7
  // Vector from A to B

8
  Vec2 n = B->pos - A->pos
9
10
  float r = A->radius + B->radius
11
  r *= r
12
13
  if(n.LengthSquared( ) > r)
14
    return false
15
16
  // Circles have collided, now compute manifold

17
  float d = n.Length( ) // perform actual sqrt

18
19
  // If distance between circles is not zero

20
  if(d != 0)
21
  {
22
    // Distance is difference between radius and distance

23
    m->penetration = r - d
24
25
    // Utilize our d since we performed sqrt on it already within Length( )

26
    // Points from A to B, and is a unit vector

27
    c->normal = t / d
28
    return true
29
  }
30
31
  // Circles are on same position

32
  else
33
  {
34
    // Choose random (but consistent) values

35
    c->penetration = A->radius
36
    c->normal = Vec( 1, 0 )
37
    return true
38
  }
39
}

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

ААВВ Ст. ААВВ

 ААВВ в ААВВ тест є трохи більш складним, ніж коло проти кола.  Зіткнення нормального не буде вектор з A в B, але буде особа нормальне. У ААВВ-це коробка з чотирма особами. Кожна особа має нормальний.  Це нормально являє собою одиничний вектор, який перпендикулярний до лиця.

 Вивчити загальне рівняння лінії в 2D:

 \[ ах + ьу + з = 0 \\ нормальний = завжди \begin{bmatrix} \а\б\кінець{bmatrix} \]

custom-physics-line2dcustom-physics-line2dcustom-physics-line2d

 У наведеному вище рівнянні, A і B-вектор нормалі до лінії, а вектор (А, B) передбачається нормованої (довжина вектора дорівнює нулю).  Знову ж таки, наші зіткнення нормальне (напрям вирішення конфлікту) буде в напрямку одного з нормалей до граней.

Ви знаєте, що c являє собою загальне рівняння лінії?  з-відстань від джерела.  Це дуже корисно для тестування, щоб побачити, чи точка знаходиться на одній стороні рядка чи іншої, як ви побачите в наступній статті

Тепер все, що необхідно, щоб з'ясувати, які особи зустрічних на одному з об'єктів з іншим об'єктом, і у нас нормальні.  Однак іноді кілька граней двох AABBs можуть перетинатися, наприклад, двох кутів перетинаються один з одним.  Це означає, що ми повинні знайти вісь найменшого проникнення.

Two axes of penetration the horizontal x axis is axis of least penetration and this collision should be resolved along the x axisTwo axes of penetration the horizontal x axis is axis of least penetration and this collision should be resolved along the x axisTwo axes of penetration the horizontal x axis is axis of least penetration and this collision should be resolved along the x axis

 Дві осі проходки; горизонтальна вісь є віссю X принаймні проникнення і ця колізія повинна вирішуватися уздовж осі X.

 Ось повний алгоритм ААВВ в ААВВ колектор покоління і виявлення зіткнень:

custom-physics-aabb-diagramcustom-physics-aabb-diagramcustom-physics-aabb-diagram
1
bool AABBvsAABB( Manifold *m )
2
{
3
  // Setup a couple pointers to each object

4
  Object *A = m->A
5
  Object *B = m->B
6
 
7
  // Vector from A to B

8
  Vec2 n = B->pos - A->pos
9
 
10
  AABB abox = A->aabb
11
  AABB bbox = B->aabb
12
 
13
  // Calculate half extents along x axis for each object

14
  float a_extent = (abox.max.x - abox.min.x) / 2
15
  float b_extent = (bbox.max.x - bbox.min.x) / 2
16
 
17
  // Calculate overlap on x axis

18
  float x_overlap = a_extent + b_extent - abs( n.x )
19
 
20
  // SAT test on x axis

21
  if(x_overlap > 0)
22
  {
23
    // Calculate half extents along x axis for each object

24
    float a_extent = (abox.max.y - abox.min.y) / 2
25
    float b_extent = (bbox.max.y - bbox.min.y) / 2
26
 
27
    // Calculate overlap on y axis

28
    float y_overlap = a_extent + b_extent - abs( n.y )
29
 
30
    // SAT test on y axis

31
    if(y_overlap > 0)
32
    {
33
      // Find out which axis is axis of least penetration

34
      if(x_overlap > y_overlap)
35
      {
36
        // Point towards B knowing that n points from A to B

37
        if(n.x < 0)
38
          m->normal = Vec2( -1, 0 )
39
        else
40
          m->normal = Vec2( 0, 0 )
41
        m->penetration = x_overlap
42
        return true
43
      }
44
      else
45
      {
46
        // Point toward B knowing that n points from A to B

47
        if(n.y < 0)
48
          m->normal = Vec2( 0, -1 )
49
        else
50
          m->normal = Vec2( 0, 1 )
51
        m->penetration = y_overlap
52
        return true
53
      }
54
    }
55
  }
56
}

 Коло проти ААВВ

 Останній тест я закрию тест коло проти ААВВ.  Ідея тут полягає в тому, щоб обчислити найближчу точку на ААВВ кола; звідти тест переходить у щось схоже на коло проти тіста круг.  Як тільки найближча точка обчислюється і зіткнення виявляється нормальний напрямку найближчої точки до центра кола.  Глибина проникнення-це різниця між відстанню від найближчої точки на колі і радіус кола.

AABB to Circle intersection diagramAABB to Circle intersection diagramAABB to Circle intersection diagram
 ААВВ з круговою діаграмою перетину.

 Є один хитрий особливий випадок; якщо центр кола знаходиться в межах ААВВ потім в центр кола повинен бути закріплений до найближчої кромці ААВВ, а нормальна повинна бути перевернута.

1
bool AABBvsCircle( Manifold *m )
2
{
3
  // Setup a couple pointers to each object

4
  Object *A = m->A
5
  Object *B = m->B
6
7
  // Vector from A to B

8
  Vec2 n = B->pos - A->pos
9
10
  // Closest point on A to center of B

11
  Vec2 closest = n
12
13
  // Calculate half extents along each axis

14
  float x_extent = (A->aabb.max.x - A->aabb.min.x) / 2
15
  float y_extent = (A->aabb.max.y - A->aabb.min.y) / 2
16
17
  // Clamp point to edges of the AABB

18
  closest.x = Clamp( -x_extent, x_extent, closest.x )
19
  closest.y = Clamp( -y_extent, y_extent, closest.y )
20
21
  bool inside = false
22
23
  // Circle is inside the AABB, so we need to clamp the circle's center

24
  // to the closest edge

25
  if(n == closest)
26
  {
27
    inside = true
28
29
    // Find closest axis

30
    if(abs( n.x ) > abs( n.y ))
31
    {
32
      // Clamp to closest extent

33
      if(closest.x > 0)
34
        closest.x = x_extent
35
      else
36
        closest.x = -x_extent
37
    }
38
39
    // y axis is shorter

40
    else
41
    {
42
      // Clamp to closest extent

43
      if(closest.y > 0)
44
        closest.y = y_extent
45
      else
46
        closest.y = -y_extent
47
    }
48
  }
49
50
  Vec2 normal = n - closest
51
  real d = normal.LengthSquared( )
52
  real r = B->radius
53
54
  // Early out of the radius is shorter than distance to closest point and

55
  // Circle not inside the AABB

56
  if(d > r * r && !inside)
57
    return false
58
59
  // Avoided sqrt until we needed

60
  d = sqrt( d )
61
62
  // Collision normal needs to be flipped to point outside if circle was

63
  // inside the AABB

64
  if(inside)
65
  {
66
    m->normal = -n
67
    m->penetration = r - d
68
  }
69
  else
70
  {
71
    m->normal = n
72
    m->penetration = r - d
73
  }
74
75
  return true
76
}

 Висновок

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

  • Контактної пари сортування та вибраковування
  • Broadphase
  •  Нашаровувати
  • Інтеграція
  • Timestepping
  •  Перетин півпростір
  • Модульна конструкція (матеріали, маси та сили)

 Я сподіваюся, вам сподобалася ця стаття і я з нетерпінням чекаю відповідей на питання в коментарях.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.