Виявлення зіткнень
Щоб об'єкти відскакували правильно, треба розв'язати дві задачі: які пари об'єктів можуть торкатися (широка фаза) і як саме вони торкаються (вузька фаза). Алгоритми, що стоять за AABB, деревами BVH, SAT, GJK та EPA, є основою кожного фізичного рушія.
1. Двофазний конвеєр
За N об'єктів наївна перевірка пар O(N²) надто повільна. Сучасні рушії розбивають виявлення зіткнень на дві фази:
2. Широка фаза — дерева BVH та AABB
Обмежувальні паралелепіпеди, вирівняні за осями (AABB)
Кожна форма загортається в паралелепіпед, вирівняний за осями світу. Два AABB перекриваються тоді й лише тоді, коли вони перекриваються одночасно за всіма 3 осями — перевіряється 6 порівняннями.
Ієрархія обмежувальних об'ємів (BVH)
BVH — це двійкове дерево, де кожен вузол зберігає AABB, що охоплює всіх його нащадків. Стратегія побудови (низхідна SAH):
- Оберіть вісь розбиття (найдовша вісь поточного AABB).
- Відсортуйте примітиви вздовж цієї осі.
- Знайдіть позицію розбиття, що мінімізує евристику площі поверхні (SAH): вартість = N_left · Area_left + N_right · Area_right.
- Рекурсивно повторюйте для кожної половини, доки розмір листа ≤ порогу (~4 трикутники).
Обхід: починаємо з кореня. Якщо немає перекриття з AABB вузла, відсікаємо все піддерево. Інакше рекурсивно заходимо в нащадків. У середньому O(log N) на запит; результат — O(K) пар-кандидатів, де K — фактична кількість перекриттів.
3. Вузька фаза — SAT
Теорема про розділювальну вісь стверджує, що дві опуклі форми не перетинаються тоді й лише тоді, коли існує розділювальна вісь — напрямок, уздовж якого їхні проєкції не перекриваються.
Для двох опуклих багатокутників A і B кандидатами на розділювальні осі є нормалі до ребер обох форм. Алгоритм:
- Для кожної нормалі до ребра A спроєктуйте всі вершини A і B на цю вісь.
- Якщо спроєктовані інтервали не перекриваються → форми розділені → повертаємо false (немає зіткнення).
- Повторіть для нормалей до ребер B.
- Якщо за всіма осями є перекриття → форми ЗІШТОВХУЮТЬСЯ. Відстежуйте вісь мінімального перекриття → нормаль проникнення.
Для 3D опуклих багатогранників: перевіряйте нормалі граней обох форм ПЛЮС векторні добутки пар ребер (ребро з A × ребро з B). Усього осей = F_A + F_B + E_A × E_B. Дострокове завершення на першій розділювальній осі робить це швидким на практиці.
4. Алгоритм GJK
Алгоритм Гілберта-Джонсона-Кірті є галузевим стандартом для зіткнень опуклих форм у 3D. Ключова ідея: дві опуклі форми A і B не перетинаються тоді й лише тоді, коли початок координат не міститься в різниці Мінковського A ⊕ (−B).
Різниця Мінковського M = {a − b | a ∈ A, b ∈ B} також опукла. GJK знаходить точку в M, найближчу до початку координат, використовуючи лише опорну функцію: для напрямку d повертає вершину M, що сягає найдалі в цьому напрямку.
GJK будує симплекс (точка → відрізок → трикутник → тетраедр у 3D), що наближається до початку координат. На кожному кроці він запитує: чи містить поточний симплекс початок координат? Якщо так — зіткнення. Якщо ні — знаходить наступний напрямок пошуку до початку координат і додає нову опорну точку. Збігається за O(1) ітерацій для більшості форм.
Опорні функції тривіально швидкі для сфери (r·d̂), капсули, паралелепіпеда (sign(d)·halfExtents) та опуклої оболонки (сходження на пагорб за O(√N)). Саме тому GJK обробляє довільні опуклі форми без попереднього обчислення різниці Мінковського.
5. EPA — контактні многовиди
GJK каже вам, чи форми зіштовхуються, але не глибину проникнення й не контактну нормаль. Алгоритм розширення політопа спирається на симплекс GJK, коли виявлено зіткнення:
- Почніть із симплекса від GJK (тетраедр, що містить початок координат).
- Знайдіть грань політопа, найближчу до початку координат (напрямок мінімального проникнення).
- Розширте політоп, знайшовши опорну точку в напрямку зовнішньої нормалі грані.
- Замініть грань двома новими трикутниками, що з'єднуються з новою точкою.
- Повторюйте, доки жодна нова опорна точка не виходить далі → збіжність. Найближча грань дає нормаль і глибину проникнення.
Маючи нормаль і глибину, ви можете побудувати контактний многовид: множину точок контакту (для стабільної симуляції об'єктів у спокої, особливо паралелепіпедів, потрібно 1–4 точки контакту, а не лише одна). Відсікайте за областями Вороного обох форм, щоб знайти контактну ділянку.
6. Неперервне виявлення зіткнень
Швидкі об'єкти можуть протунелювати крізь тонкі об'єкти між кадрами (куля, що проходить крізь стіну). Неперервне виявлення зіткнень (CCD) розв'язує це, протягуючи форми вздовж їхніх траєкторій.
- Протягнута сфера/капсула проти площини: точний розв'язок у замкненій формі. Використовується для контролерів персонажа гравця.
- Консервативне просування (Міртіч): ітеративно просуває обидві форми до зіткнення, використовуючи нижню межу відстані. Гарантовано збігається без тунелювання.
- Спекулятивні контакти (Като): на кожному кадрі обчислюється відстань до найближчої точки. Якщо об'єкт рухається за кадр більше за цю відстань, генерується спекулятивний контакт. Дешевше за справжнє CCD; використовується в Box2D і Jolt.
CCD додає витрат — більшість рушіїв обмежують його: лише швидкі/малі об'єкти (з високим співвідношенням швидкості до розміру) вмикають CCD, що перемикається прапорцем «bullet».
7. Нотатки щодо реалізації
- Засинання: об'єкти, що не рухалися протягом N кадрів, позначаються як сплячі й виключаються з виявлення зіткнень, доки сусід їх не розбудить. Величезний виграш у продуктивності для великих сцен.
- Шари та маски: бітове поле шару зіткнень (наприклад, шар Physics, шар Trigger, шар Player). Пара перевіряється лише якщо шари мають спільний біт: (layerA & maskB) != 0.
- Розв'язання островів: побудуйте граф пар тіл, що зіштовхуються. Розв'язуйте кожну зв'язну компоненту (острів) незалежно за допомогою ітеративного розв'язувача обмежень (PGS, XPBD або TGS).
- Числові допуски: GJK/EPA потребують обережного поводження з epsilon. Розділення 1e-6 м проти 0 має значення. Використовуйте «допустиме проникнення» (slop) ~0.005 м, щоб уникнути тремтіння.
- Популярні бібліотеки: Jolt Physics (C++, відкритий код, використана в Horizon Zero Dawn), Bullet (C++, MIT), PhysX (NVIDIA), Box2D (2D). Для WebAssembly: Jolt WASM, Ammo.js (порт Bullet).