CPU3D.com2D графикаСобытия → Физические движки для чайников

Физические движки для чайников

Сегодня мы поговорим об основных компонентах, которые составляют физический движок и о том, как сложить их вместе. Этот туториал предназначен для программистов, у которых есть базовые познания в математике и геометрии, и которые хотят окунуться в мир моделирования.

Я надеюсь, что если до этого вы могли сделать разве что физику для игры Pong 1972-го года, то после прочтения этой статьи вы легко сможете написать свои ограничения для ваших физических движков.

Вы можете написать код для этого?

Я всегда считал название серии книг «… для чайников» обнадеживающим. Ведь если даже чайник сможет это изучить, то у вас и подавно получится, верно?

… для чайников?

Отсюда и название этого туториала.
План действий

Итак, я собираюсь осветить несколько вещей о физических движках, которые вы, возможно, захотите узнать:

— Абсолютно твёрдые тела
— Коллизии
— Трение покоя
— Ограничения (Соединения)
Моделирование

Имейте в виду, что мы начнем с самых азов. Надеюсь, в дальнейшем станет ясно, почему.

Начнем с частиц, которые, предположим, имеют позицию P и скорость V. Каждый кадр мы изменяем позицию P, добавляя к ней скорость V. Этот процесс называется интегрированием.

Вы можете использовать любые единицы, подходящие вашей модели. Обычно выбор падает на ед/метр, что я в данном случае и использую. Экран имеет размер два на два метра (в нашем мире), поэтому скорость определяется в метрах в секунду. Чтобы наш пример заработал, нам нужно знать, число секунд в кадре. Теперь, для правильного движения частиц воспользуемся формулой P += V * dt, где dt — время (в секундах), прошедшее с предыдущего кадра.

Можно сымитировать движение множества частиц, сохраняя массивы их позиций и скоростей, и циклически интегрируя каждый элемент в них. Чтобы предотвратить выход частиц из области видимости, мы заставим их отталкиваться от краев экрана.

Для того, чтобы частицы отскакивали от краев мы просто должны отслеживать столкновения с краями экрана и менять их скорость, по оси столкновения, на противоположную.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 for all i
if (P[i].x < -1 && V[i].x < 0)
{
V[i].x = -V[i].x;
}
if (P[i].y > 1 && V[i].y > 0)
{
V[i].y = -V[i].y;
};
if (P[i].x > 1 && V[i].x > 0)
{
V[i].x = -V[i].x;
}
if (P[i].y < -1 && V[i].y < 0)
{
V[i].y = -V[i].y;
}

В каждом блоке первое условие определяет пересечение частицы в пространстве, а второе проверяет, направляется ли частица к краю. Второе условие очень важно, так как в противном случае столкновение будет срабатывать снова, и в следующем кадре частицы будут ошибочно двигаться со скоростью, большей чем один пиксель в кадр. Таким образом, оно выполняет имитацию твердого тела на основе импульса, и отделяет ограничения-неравенства (например, касание) и ограничения-равенства (большинство соединений).

Это пример кода, который вы, наверняка, уже давным-давно написали, только начав интересоваться физическими моделями. Я включил его в туториал, поскольку считаю, естественным переход от этого простого примера к более сложной физике.
Что происходит в данной простейшей модели?

Сами того не зная, мы присвоили нашим частицам такой тип физического материала, который включает в себя коэффициент восстановления (EN/RU) и подчиняется закону сохранения импульса (EN/RU). Кроме того, мы обозначили, что мир имеет бесконечную массу, таким образом он никак не реагирует при воздействии частиц на него. Отражая частицы при ударе, мы сохраняем их импульс (проигнорировав их массу в вычислениях), указывая этим, что коэффициент восстановления частицы равен единице, то есть, как у совершенно упругого супер-мяча. В добавок, для реакции на столкновение, мы предпочли модель импульса/скорости модели силы/ускорения, изменяя скорость частиц мгновенно, а не за определенный промежуток времени.

На самом деле, здесь мы имеем дело с высоко оптимизированным случаем имитации физики. «Каким образом оптимизированный?» — спросите вы. Позвольте мне объяснить:

Если бы мы решили написать вышеприведенный код «по уму», избегая грубых упрощений, нам мы бы пришлось учитывать следующее. Наша среда определена ее границами — четырьмя плоскостями выровненными по осям (фактически — это линии, так как мы находимся в двухмерном мире). Каждая из этих плоскостей имеет нормаль, указывающую внутрь, и расстояние до начала координат. Они выглядят так:
1
2
3
4
5
6
7 Planes[4] =
{
(1, 0, 1),
(0, -1, 1),
(-1, 0, 1),
(0, 1, 1)
}



Теперь мы должны определить столкновения, как мы делали это ранее, но на этот раз мы не будем ничего упрощать . Чтобы обнаружить пересечение частиц с плоскостями, мы должны выполнить скалярное произведение (EN/RU) и добавить расстояние до начала координат.
1
2
3
4
5
6
7
8
9
10
11
12 for all particles i
{
for all planes j
{
distance = P[i].x*Planes[j].a + P[i].y*Planes[j].b + Planes[j].c;

if (distance < 0)
{
// collision responce
}
}
}

Этот код определяет длину проекции (EN/RU) вектора направленного от плоскости к частице на нормаль плоскости. А поскольку нормали наших плоскостей имеют единичную длину, это значение является расстоянием от частицы до плоскости. Очевидно, что если вычисленное расстояние оказалось меньше нуля, то наша частица проникла в плоскость и нам следует среагировать на столкновение.

Теперь, если мы внимательнее, включая коэффициенты каждой плоскости, изучим код расчета расстояния, приведенный выше:
1
2
3
4 plane0dist = P[i].x*1 + P[i].y*0 + 1;
plane1dist = P[i].x*0 + P[i].y*-1 + 1;
plane2dist = P[i].x*-1 + P[i].y*0 + 1;
plane3dist = P[i].x*0 + P[i].y*1 + 1;

Немного упростив,
1
2
3
4 plane0dist = P[i].x + 1;
plane1dist = -P[i].y + 1;
plane2dist = -P[i].x + 1;
plane3dist = P[i].y + 1;

и чуть-чуть преобразовав, мы получим условия проверки для этих плоскостей
1
2
3
4 if (P[i].x < -1)
if (-P[i].y < -1) = if (P[i].y > 1)
if (-P[i].x < -1) = if (P[i].x > 1)
if (P[i].y < -1)

Эти условия оказались точь в точь такими же, как и в нашем первом, простом, примере. Второе условие также должно быть выполнено. С помощью него мы реагируем на столкновение только один раз, в момент столкновения. Это можно сделать выполнив скалярное умножение скорости частиц, на нормаль к каждой плоскости.
1 if (P[i].x < -1 && V[i]•N[i] < 0)
Мы назовем это перпендикулярная скорость, поскольку это скорость частицы по направлении к нормали. Если значение этой величины оказывается меньше нуля, то это значит, что частица движется по направлению к плоскости и мы фиксируем столкновение. Как только столкновение будет обработано, перпендикулярная скорость станет больше или равна нулю, в зависимости от коэффициента восстановления. Я бы мог провести детальный анализ этого условия, как делал это выше, чтобы доказать что в конечном итоге решение будет совпадать с первой версией кода, но оставлю это в качестве упражнения для читателей.

Это свидетельствует от том, что оба наших подхода являются, фактически, одним и тем же.

Хорошо, как же в данном случае нам обрабатывать столкновение?

Нам нужно решение, которое даст тот же результат, что и исходная программа, но которое, в тоже время, будет являться полным. Мы можем сделать это с помощью вектора отражения (EN) из закона отражения (EN/RU).



Источник: http://ninniah.ru