Як створити користувальницький 2D-фізичний движок: Основи та імпульсну роздільну здатність
() translation by (you can also view the original English article)
Є багато причин, ви, можливо, захочете, щоб створити настроюваний фізичний движок: по-перше, вивчаючи і відточуючи свої навички в області математики, фізики і програмування великих причин для спроб такого проекту; по-друге, настроюється движок може вирішити яке-небудь технічний ефект творець володіє вмінням створити. У цій статті я хотів би забезпечити найкраще уявлення про те, як створити користувальницький повністю фізичний движок з нуля.
Фізика служить прекрасним засобом, який дозволяє гравцеві повністю зануритися в гру. Це має сенс, що оволодіння фізичний движок буде потужним активом для будь-якого програміста, щоб мати в своєму розпорядженні. Оптимізації та спеціалізації можуть бути зроблені в будь-який час за рахунок глибокого розуміння внутрішньої роботи фізичного движка.
До кінця цього підручника у наступних розділах будуть покриті, в двох вимірах:
- Просте виявлення зіткнень
- Простий колектор покоління
- Імпульсна роздільна здатність
Ось короткий демо:
Примітка: хоча цей підручник написаний на C++, ви повинні бути в змозі використовувати ті ж методи і концепції практично в будь-якому середовищі розробки.
Передумови
Ця стаття включає досить велику кількість математики і геометрії, і в набагато меншому ступені фактичного кодування. Пару передумов для цієї статті є:
- Базове розуміння простої векторної математики
- Уміння виконувати алгебраїчні математика
Виявлення Зіткнень
Є чимало статей і посібників по всьому інтернету, в тому числі і тут на Tuts+, які охоплюють виявлення зіткнень. Знаючи це, я хотів би пробігтися по темі дуже швидко, так як ця ділянка не є темою цієї статті.
Осі Вирівняні Обмежують Прямокутників
Осі вирівняні прямокутника (ААВВ) - це вікно, яке має чотири осі поєднана з системою координат, в якій він проживає. Це означає, що це поле, яке не може обертатися, і завжди квадрат на 90 градусів (зазвичай вирівнюється з екрану). В цілому це називається "прямокутник", тому що AABBs використовуються для пов'язані інші, більш складні форми.



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



Для досягнення таких ефектів і слідкуйте за поряд з природною інтуїцією, як ведуть себе об'єкти ми будемо використовувати тверді тіла і трохи математики. Тверде тіло-це просто форма, визначених користувачем (тобто вами, забудовника), який неявно певними, щоб не деформуватися. Обидва AABBs і кола в цій статті, недеформіруемой, і завжди буде або ААВВ або коло. Ніякого стиснення або розтягування допускається.
Робота з твердими тілами дозволяє багато математики і деривації повинні бути сильно спрощена. Саме тому тверді тіла широко використовуються в ігрових симуляторах, і тому ми будемо їх використовувати в цій статті.
Наші Об'єкти Зіткнувся - Тепер Що?
Припустимо, що ми маємо дві фігури виявляються пересічними, як насправді відокремити два? Припустимо, що наш виявлення зіткнень дав нам дві важливі частини.
- Зіткнення нормально
- Глибина проникнення
Для того, щоб застосувати імпульс до об'єктів, але і переміщати їх один від одного, ми повинні знати, в якому напрямку штовхати їх і на скільки. Нормальне зіткнення напрямок, в якому імпульс буде застосовуватися. Глибина проникнення (разом з деякими іншими речами) визначити, як великі імпульсу буде використовуватися. Це означає, що єдина цінність, яку потрібно вирішити для масштабів нашого імпульсу.
Тепер давайте повернемося в далекий похід, щоб дізнатися, як ми можемо вирішити для цієї величини імпульсу. Ми почнемо з наших двох об'єктів, які були знайдені, щоб бути перетинаються:
\[ В^{АВ} = V і^Б - В^А \] зауважимо, що для того, щоб створити вектор з позиції a в позицію B, ви повинні зробити: кінцева точка - Вихідна позиція. \(^{АВ}\) - відносна швидкість з А в Б. Це рівняння має бути виражена в термінах зіткнення нормальний \(н\) - тобто, ми хотіли б знати відносну швидкість від А до Б уздовж лінії зіткнення нормального напрямки:
\[ В^{АВ} \cDOT на Н = (^Б - В^А) \cDOT на п \]
Ми зараз використовуємо скалярний добуток. Скалярний добуток простих; це сума по компонентно продуктів:
\[ В_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 ) |
Після того як \Е\) є придбаним, ми можемо розмістити його в нашому вирішуючи рівняння для величини імпульсу.
Закон Ньютона про реституцію говориться наступне:
\[В' = Е * \у]
Все це говорить, що після зіткнення швидкість дорівнює швидкості перед ним, помноженої на деяку константу. Ця константа являє собою "коефіцієнт відскоку". Знаючи це, він стає досить простий для інтеграції реституції в нашій нинішній деривації:
\[ В^{АВ} \cDOT на Н = -Е * ^Б - В^А) \cDOT на п \]
Зверніть увагу, як ми ввели тут негативний знак. В закон Ньютона про реституцію, \(\), отримаємо результуючий вектор після відмов, що насправді відбувається в напрямку, протилежному Ст. Так як ми представляємо протилежних напрямках в нашому деривації? Ввести негативний знак.
Досі так добре. Тепер ми повинні бути в змозі висловити ці швидкості в той час як під впливом імпульсу. Ось просте рівняння для зміни вектора яким-імпульсу скалярного \(\) уздовж певної \напрям(Н\):
\[ У' = у + J В * Н \]
Сподіваюся, що наведене вище рівняння має сенс, так як це дуже важливо розуміти. У нас є одиничний вектор \(Н\), який являє собою напрям. У нас є скаляр \(\), яка представляє як довго наші \(Н\) вектор буде. Потім ми додаємо наш масштабується \(Н\) вектор \(\v), щоб привести в \(\). Це просто додавання одного вектора на інший, і ми можемо використовувати цей невеликий рівняння, щоб застосувати імпульс від одного вектора до іншого.
Є трохи більше роботи, щоб бути зроблено тут. Формально, імпульс визначається як зміна імпульсу. Імпульс-це маса * швидкість. Знаючи це, ми можемо уявити імпульс як формально визначається так:
\[ Імпульс = маса * швидкість \\ швидкість = \фрац{імпульс}{маса} \отже, у' = у + \фрац{Дж * Н}{маса}\]
Хороший прогрес був досягнутий досі! Однак ми повинні бути в змозі висловити імпульс через \(\) щодо двох різних об'єктів. При зіткненні з об'єктом A і B, а зсуваються в протилежному напрямку Б:
\[ В'^У = ^А + \фрац{Дж * Н}{мас^а} \ \ '^У = ^Б - \фрац{Дж * Н}{маса^Б} \]
Ці два рівняння будуть штовхати від Б вздовж напрямку одиничного вектора \(н\) з імпульсним скаляр (величина \(н\)) \(\).
Все, що зараз потрібно, це об'єднати рівняння 8 і 5. Наші отримане рівняння буде виглядати так:
\ [ (^А - ^ + \фрац{Дж * Н}{мас^а} + \фрац{Дж * Н}{маса^б}) * н = -е * ^Б - В^А) \cDOT на п \\ \Тому \ \ (^А - ^ + \фрац{Дж * Н}{мас^а} + \фрац{Дж * Н}{маса^Б}) * П + Е * ^Б - В^А) \cDOT на н = 0 \]
Якщо ви пам'ятаєте, первісна мета полягала в тому, щоб ізолювати наші масштаби. Це тому, що ми знаємо в якому напрямку вирішення конфлікту (передбачається, дане виявлення зіткнень), і залишилося лише вирішити масштаби цього напрямку. Величина, яка є невідомою в нашому випадку \(\), ми повинні ізолювати \(\) і видаліть його.
\[ (П^Б - В^А) \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). Ця перевірка дуже важлива, вона гарантує, що ви тільки дозволяти колізію, якщо об'єкти рухаються назустріч один одному.



Якщо об'єкти розлітаються одна від одної, ми хочемо нічого не робити. Це дозволить виключити об'єкти, які не повинні бути розглянуті, стикаючись з дозволу один від одного. Це важливо для створення моделювання, які слід людську інтуїцію на те, що повинно відбутися в процесі взаємодії об'єктів.
Друге застереження полягає в тому, що зворотній маси обчислюється кілька разів без причини. Краще всього зберігати ваші зворотних мас у межах кожного об'єкта і попередньо обчислити його один раз:
1 |
A.inv_mass = 1 / A.mass |
Остання річ, щоб зазначити, що ми грамотно розподілити наші імпульсу скалярного \(\) на два об'єкта. Ми хочемо, щоб невеликі об'єкти відскакують на великі об'єкти з великою часткою \(\), і великі об'єкти, щоб їх швидкостей змінюються дуже невелику частину \(\).
Для того щоб зробити це, ви могли б зробити:
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} \]



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



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



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



Є один хитрий особливий випадок; якщо центр кола знаходиться в межах ААВВ потім в центр кола повинен бути закріплений до найближчої кромці ААВВ, а нормальна повинна бути перевернута.
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
- Перетин півпростір
- Модульна конструкція (матеріали, маси та сили)
Я сподіваюся, вам сподобалася ця стаття і я з нетерпінням чекаю відповідей на питання в коментарях.