Пошук контактної точки за допомогою SAT


12

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

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

САТ-діаграма

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

Чи є більш простий і / або більш ефективний спосіб знайти вектор контактної точки?


1
Ви готові використовувати SAT? Такі алгоритми, як MPR (уточнення порталу Minkowski), можуть знайти контактний колектор безпосередньо. Для SAT та GJK вам потрібен окремий алгоритм для обчислення точок контактів.
Шон Міддлічч

Відповіді:


6

Те, про що ви говорите, досить складно, якщо ви структуруєте його як спочатку переміщення об'єкта, потім тестування на зіткнення, а потім відступ, поки ви не вийдете з об'єкта. Це, мабуть, краще думати про це як пробу динамічного перетину : рухомий об’єкт проти нерухомого об’єкта.

На щастя, тут можна допомогти роздільним тестам на осі! Ось опис алгоритму, ввічливий Рон Левін :

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

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

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

t_min := +∞
t_max := -∞
foreach axis in potential_separating_axes
    a_min := +∞
    a_max := -∞
    foreach vertex in a.vertices
        a_min = min(a_min, vertex · axis)
        a_max = max(a_max, vertex · axis)
    b_min := +∞
    b_max := -∞
    foreach vertex in b.vertices
        b_min = min(b_min, vertex · axis)
        b_max = max(b_max, vertex · axis)
    v := b.velocity · axis
    if v > 0 then
        if a_max < b_min then
            return no_intersection
        else if (a_min < b_min < a_max) or (b_min < a_min < b_max) then
            t_min = min(t_min, (a_max - b_min) / v)
            t_max = max(t_max, 0)
        else
            t_min = min(t_min, (a_max - b_min) / v)
            t_max = max(t_max, (a_min - b_max) / v)
    else if v < 0 then
        // repeat the above case with a and b swapped
    else if v = 0 then
        if a_min < b_max and b_min < a_max then
            t_min = min(t_min, 0)
            t_max = max(t_max, 0)
        else
            return no_intersection
if t_max < t_min then
    // advance b by b.velocity * t_max
    return intersection
else
    return no_intersection

Ось стаття про Гамасутру, яка розповідає про це, здійснене для кількох різних примітивних тестів. Зауважте, що як і SAT, для цього потрібні опуклі об’єкти.

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


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

1
@seanmiddleditch Це правда, вона нехтує обертаннями кадрів. Ви повинні повернути миттєво на початку. Але жоден метод, про який я знаю, не маючи консервативного прогресу, насправді точно не стосується ротацій. Однак, якщо немає обертання, це дає кращу оцінку точки контакту.
Джон Калсбек

2

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

http://www.codezealot.org/archives/394

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

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

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


Чи знаєте ви, чи існує спосіб обчислити той "синій вектор" (той, який необхідний для відштовхування об'єкта поза формою уздовж вектора швидкості) за допомогою SAT?
Тара

@Dudeson: не використовується SAT, ні. Це не те, що робить SAT. SAT дає вам край мінімальної глибини проникнення, а не перший контактний край. Вам потрібно буде використовувати розмитнення форми зіткнення форми, щоб зробити те, що ви просите, я думаю.
Шон Міддлічч

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

@Dudeson: Край / вісь найменшого проникнення не обов'язково є краєм першого контакту, тому я досі не бачу, як SAT допомагає тут. Я аж ніяк не експерт у цій темі, тож визнаю, що міг просто помилитися. :)
Шон Міддлічч

Саме так. Тому я не впевнений, чи можливо це навіть можливо. Але це означає, що відповідь якогось хлопця просто неправильна. Але дякую за допомогу все одно! : D
Тара

0

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


1
"MAT Vector"? Ви маєте на увазі "MTV"?
Тара

0

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

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

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

vec3 vertex;
float mindot = FLT_MAX;
for ( vert : vertices )
{
    if (dot(vert, MTV) < mindot)
    {
         mindot = dot(vert, MTV);
         vertex = vert;
    }
}

Після того, як ви встановите вершину, двійковий півшарок стає набагато дешевшим:

//mindistance is the where the reference edge/plane intersects it's own normal. 
//The max dot product of all vertices in B along the MTV will get you this value.
halfstep = 1.0f;
vec3 cp = vertex;
vec3 v = A.velocity*framedurationSeconds;
float errorThreshold = 0.01f; //choose meaningful value here
//alternatively, set the while condition to be while halfstep > some minimum value
while (abs(dot(cp,normal)) > errorThreshold)
{            
    halfstep*=0.5f;
    if (dot(cp,normal) < mindistance) //cp is inside the object, move backward
    {
        cp += v*(-1*halfstep);
    }
    else if ( dot(cp,normal) > mindistance) //cp is outside, move it forward
    {
        cp += v*(halfstep);
    }
}

return cp;

Це досить точно, але забезпечить лише одну точку зіткнення, в одному випадку.

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

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

Цікаво, що цей метод на півкроковому кроці також може дати вам (майже) точний час, коли об’єкт стався під час кадру:

float collisionTime = frametimeSeconds * halfstep;

Якщо ви виконуєте якусь фізичну роздільну здатність зіткнення, ви можете виправити положення A за допомогою:

v - (v*halfstep)

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

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