Как написать физический движок

Физический движок (англ. physics engine ) — компьютерная программа, которая производит компьютерное моделирование физических законов реального мира в виртуальном мире, с той или иной степенью аппроксимации. Чаще всего физические движки для физического моделирования используются не как отдельные самостоятельные программные продукты, а как составные компоненты (подпрограммы) других программ.

Все физические движки условно делятся на два типа: игровые и научные.

  • Первый тип используется в компьютерных играх как компонент игрового движка. В этом случае он должен работать в режиме реального времени, то есть воспроизводить физические процессы в игре с той же самой скоростью, в которой они происходят в реальном мире. Вместе с тем от игрового физического движка не требуется точности вычислений. Главное требование — визуальная реалистичность, и для его достижения не обязательно проводить точную симуляцию. Поэтому в играх используются очень сильные аппроксимации, приближенные модели и другие приёмы.
  • Научные физические движки используются в научно-исследовательских расчётах и симуляциях, где крайне важна именно физическая точность вычислений. Вместе с тем скорость вычислений не играет существенной роли.

Современные физические движки симулируют не все физические законы реального мира, а лишь некоторые, причём с течением времени и прогресса в области информационных технологий и вычислительной техники список «поддерживаемых» законов увеличивается. На начало 2010 года физические движки могут симулировать следующие физические явления и состояния:

В августе 2009 года англоязычный журнал Game Developer ( англ. ) , посвящённый разработке компьютерных игр, опубликовал статью о современных игровых движках и их использовании. Согласно данным журнала, наиболее популярным среди разработчиков является движок nV >[1]

Содержание

Использование [ править | править код ]

Описание [ править | править код ]

Физический движок позволяет создать некое виртуальное пространство, которое можно наполнить телами (виртуальными статическими и динамическими объектами), и указать для него некие общие законы взаимодействия тел и среды, в той или иной мере приближенные к физическим, задавая при этом характер и степень взаимодействий (импульсы, силы и т. д). Собственно расчёт взаимодействия тел движок и берёт на себя. Когда простого набора объектов, взаимодействующих по определённым законам в виртуальном пространстве, недостаточно в силу неполного приближения физической модели к реальной, возможно добавлять (к телам) связи. Рассчитывая взаимодействие тел между собой и со средой, физический движок приближает физическую модель получаемой системы к реальной, передавая уточнённые геометрические данные средству отображения (рендереру).

Тело [ править | править код ]

Тело (англ. body ) — объект игровой физики, который определяется:

  • его формой (есть простые формы: шар, куб, цилиндр; есть сложные формы, набор которых в разных движках может различаться);
  • неким набором параметров (масса, упругость, коэффициент трения, инертность по осям).

Связь [ править | править код ]

Связь (соединение; англ. joint ) — ограничения объектов игровой физики, каждое из которых может накладываться на одно или два тела.

Взаимодействие [ править | править код ]

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

Привет дорогой друг! В прошлой статье я говорил, что больше не буду затрагивать тему 2D игр на XNA. Пожалуй, я вас обманул, но не совсем. Многие начинающие геймдевелоперы используют в своих физических головоломках — физический движок Box2D, о нем довольно много писали на хабре. Да что уж там новички, многие довольно опытные геймдевелоперы — его используют. Но вот мало кто знает, как на самом деле он работает. Остальное под хабракатом.

В этой статье я постараюсь подробно описать процесс создания каркаса физического движка. Конечно, получиться не совсем Box2D: различные хеш оптимизации, отсечения не будут описываться в данной статье, но будет понятно, как он работает.

Читайте также:  Как пользоваться вайбером на телефоне андроид видео

Общие принципы. Как это вообще работает?

Обычное игровое приложение каждый кадр выполняет примерно такую последовательность действий:

  • Воздействуем на физический мир
  • Обновляем физический мир
  • Отрисовываем его новое состояние
  • Повторяем

Самый интересный для нас шаг в этой схеме — второй. Именно здесь и происходит вся магия физического движка — он определяет, в какое состояние перейдёт физическая система в следующий момент времени, спустя dt (короткий промежуток времени). Этот шаг, в свою очередь, уже внутри движка разбивается ещё на три подшага:

  • Обнаружение столкновений
  • Разрешение столкновений
  • Интегририрование

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

Начнем с устройства физического движка, который мы будем писать. Наша цель — написать физику твердых тел на основе импульсов. В идеале нам хотелось бы, чтоб тело могло быть любой формы, т.е., например, такой:

На самом деле описать такую форму довольно сложно, и движки, работающие на «невыпуклых» формах, найти очень сложно, не говоря уже о 3D. Поэтому мы создадим такую систему, что тело можно будет представить любой сложной формой с помощью простых форм.

Теперь разъясню составляющие физического тела. Само «тело» в нашем движке будет просто точка, имеющая центр масс. Эта точка будет перемещаться под действием различных сил, например, гравитации. Вокруг нее (точки) могут быть «навешаны» формы. В данной статье будут рассмотрены формы выпуклых полилиний (полигонов). После прочтения статьи — вы можете добавить и свои шейпы (формы), например, различные квадратики и кружечки. При процессинге (обработке физических тел в Update) мы будем искать шейпы, которые пересеклись, т.е. «сколизились» (collision — англ., пересечение), затем искать три основных необходимых для минимального импульсного физического движка величины, это — нормаль, вдоль которой произошла коллизия, глубину проникновения одного объекта в другой и точку контакта тел.

Допустим, имеем контакт:

А вот коллизия двух выпуклых полилиний:

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

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

Т.к. в XNA многие операции над векторами у нас уже есть — мы его просто расширим, листинг расширяющегося класса:

Теперь нам нужен класс, который будет отвечать за сами объекты тел, создадим его:

Саму интерацию (движения тела, поворот тела, etc) мы будем просчитывать в другом классе, который будет ответственен за физику в целом, назовем этот класс: "World".

Этот класс в себе будет хранить список тел, будет содержать метод step, который и будет у нас за все отвечать. Рассмотрим класс:

Теперь рассмотрим код шейпа (Poly):

Теперь нужно решить проблему просчета контактов, реализуем класс Contact и метод Solve:

Кодом я старался описать основные принципы работы импульсных движков, более разверенуто можно посмотреть все исходном коде.

Итак, подведем итоги. Очевидно, что написать физический движок на основе импульсов в 2D не так сложно, как может показаться на первый взгляд. Удачи в начинаниях!

Исходный код можно скачать тут, а exe-демо тут.

— Сначала я опишу допущения, которые мы будем использовать
— Далее я вкратце опишу общие принципы, по которым вообще работает импульсный физический движок.
— Рассмотрю каждый шаг работы двига более подробно
— Далее рассмотрим, какие проблемы при написании двига возникают чаще всего и как их отлаживать
— Процесс понимания общефизических механизмов работы, которые нам понадобятся, я разделю на независимые прикладные задачи — майлстоуны, понимание каждой отдельной из них будет бесповоротно приближать нас к познанию дзена всей целостности и элегантности теормешной теории в целом 🙂
— Далее я опишу некоторые ошибочные пути, по которым так и норовят пойти начинающие разработчики и объясню, почему так лучше не делать.

Читайте также:  Как повысить фпс в армате

Любое моделирование физики подразумевает какие-то допущения, упрощения, и наш случай — не исключение. Из таких допущений кроме совсем уж интуитивно понятных стоит выделить:
— Подразумеваем, что время идёт дискретно(малыми шажками), причём точность просчёта достаточно сильно зависит от величины кванта времени. Понятно, что если мы возьмём шаг времени dt слишком маленьким, функцию обновления физики придётся вызывать 1 / dt раз в секунду, что при слишком маленьком dt может быть слишком большой цифрой и банально вылиться в тормоза 🙂 Из чисто практических соображений скажу лишь, что dt обычно нет смысла делать меньше 1 / 100 и опасно делать больше 1 / 40(может вылиться в дрожание, проваливание итп)
— Полагаем, что величина кванта времени от кадра к кадру меняется не слишком резко. В идеале она постоянна.
— Допускаем, что во время каждого кванта времени укаждого тела линейная скорость, угловая скорость, ускорения, приложенные силы и прочие показатели не меняются.
— Считаем, что в конце каждого кванта времени скорость скачкообразно изменяется в зависимости от ускорения (на величину acceleration * dt), а позиция скачкообразно меняется соответственно но величину. Мы не будем удивляться тому, что слишком быстро летящий мяч пролетает насквозь через сетку ворот, если за кадр он пролетает расстояние, сравнимое с его размерами.
— Мы не рассматриваем регулярную прецессию, полагая, что каждый квант времени тело просто-напросто вращается вокруг вектора своей угловой скорости. При желании ничто не помешает нам имплементирвать законы Эйлера динамики прецессии.
— Мы допускаем возможность того, что одно тело провалится в другое в особо экстремальных ситуациях. Это заметно, к примеру, когда используется слишком большая скорость или слишком большой шаг времени. Хинт состоит в том, что мы разрешаем соприкасающимся телам всегда проникать друг в друга на малую величину, назовём её deltaDepth(в других источниках называется skin width). Смысл такого экстравагантного допущения будет объяснён позже.

— воздействуем на физический мир
— обновляем физический мир
— отрисовываем его новое состояние
— повторяем

Самый интересный для нас шаг в этой схеме — второй. Именно здесь и происходит вся магия физ. двига — он определяет, в какое состояние перейдёт физическая система в следующий момент времени, спустя dt. Этот шаг в свою очередь, уже внутри двига разбивается ещё на три подшага:

1) Обнаружение столкновений
2) Разрешение столкновений
3) Интегририрование

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

Обнаружение столкновений

Лучше сразу научиться разграничивать понятие физического тела и его геометрического представления. Физическое тело представляется координатами своего центра масс, скоростью, массой и моментом инерции. Его геометрией может быть что угодного — многогранник, примитив, бокс, любое их сочетание или даже геометрии может не быть вообще. Общее обычно одно — геометрия задаётся в системе координат тела и когда тело передвигается, вместе с ним передвигается и геометрия. На этом шаге от нас требуется решить непростую задачу — найти точки контакта всех тел, а также нормаль каждого контакта и его глубину. Для некоторых случаев интуитивно понятно, что должно получиться — например, если пересеклись две сферы, то точкой контакта можно считать точку, находящуюся на соединяющей их центры масс линии, внутри области пересения. Нормалью возьмём ту самую соединяющую их линию, а глубиной будет сумма их радиусов минус расстояние между ними. Если бокс стоит на столе, то точками контакта можно считать точки в его вершинах, нормалью будет нормаль к поверхности стола, а глубиной — величина проникновения каждой вершины внутрь столешницы.

Читайте также:  Как отправить на флешку большой файл

К сожалению, тут не всегда всё просто и однозначно. Часто, особенно в случае глубокого взаимопроникновения и/или невыпуклых геометрий, указать конкретные точки взаимодействия тел может быть сложно. Об одном из подходов обнаружения столкновений для достаточно широкого круга геометрий — те, которые можно описать Support Mapping’ом, я более подробно рассказал в статье. Расчёт контактов для каждого типа взаимодействующих геометрий для каждого случая в своём роде уникален и одного подхода, который бы покрывал сразу все случаи, к сожалению, нет.

Разрешение столкновений

После того, как мы обнаружили все контакты, нам нужно сделать нечто, чтобы они хотя бы не становились всё глубже. В идеале хотелось бы даже невзначай их нейтрализовать(сделать так, чтобы самый глубокий контакт имел глубину ноль) и ещё желательно не нарушить при этом законы сохранения энергии и импульса. Для этого был разработан специальный механизм, ограничивающий взаимное перемещение тел, посредствам введения связей, или джойнтов. Например, для каждого контакта создаётся специальный тип джойнта — контактный, который ограничивает одну степень свободы, запрещая телам проникать друг в друга вдоль нормали контакта. Это сделать не так-то просто, придётся попотеть над матаном и вспомнить если не теормех, то хотя бы школьную физику и компонент двига, который этим занимается, называется солвером. О том, как работают импульсные солверы, я попытался более-менее доступно объяснить в другой своей статье

Интегририрование

Наверное, самый простой шаг. К этому моменту у нас уже посчитаны, какими должны быть скорости тел, чтобы они перестали друг в друга проникать, также мы опционально посчитали вектора, вектора, на которые их надо сместить(pseudovelocities), чтобы нейтрализовать пересечения и всё, что нам остаётся — это проинтегрировать скорости и ускорения на малый промежуток времени dt. Так как мы не заморачиваемся с высокими порядками точности, то используем интегратор Эйлера.

Прежде чем приступить к интегрированию положения, стоит определиться, как мы это самое положение будем задавать. Положение тела определяется координатами его центра масс и ориентацией. Если координаты центра масс — обыкновенный вектор, то с ориентацией может быть несколько сложнее. Мы предполагаем, что ориентация тела задана тремя векторами — его направлениями вправо, вверх и вдаль. Назовём их xVector, yVector, zVector. Если записать координаты(столбцы) этих векторов в строку, мы получим матрицу 3х3 — матрицу его поворота. Иногда для наглядности я буду использовать матрицу поворота всю целиком, а иногда — разложенную на эти три вектора. Так, к примеру, эти три вектора являются направлениями главных моментов инерции тела, как их использовать в такой интерпретации я расскажу чуть позже. Я специально оперирую отдельными векторами "вправо"/"вверх"/"вдаль", чтобы у читателя была возможность понимать, что же значат все эти формулы, откуда они берутся, а вопросы "с какой стороны мне домножать этот вектор на матрицу? D:" решались сами собой. После того, как читатель начинает себя уютно чувствовать в подобной арифметике, он может смело переходить на более общепринятый вид — матрицы. Там всё то же самое, просто записывается чуть короче.

Далее о скоростях. Если вектор linearVelocity — это то, на сколько смещается тело за единицу времени, то angularVelocity — это угол, на который поворачивается тело за единицу времени вокруг своего центра масс. Мы считаем, что любое тело мгновенно движется именно таким образом — перемещается на мало расстояние и поворачивается на малый угол вокруг центра масс. Учитывая то, что мы постоянно модифицируем линейную и угловую скорости во время решения контактов, это верно.

Итак, обратно к интегрированию. Сначало посчитаем, какое значение примут линейная и угловая скорости тела, спустя время dt:

Adblock
detector