It looks like this is a web page, not a feed. I looked for a feed associated with this page, but couldn't find one. Please enter the address of your feed to validate.

Source: https://gamedev.ru/code/articles/?id=4706

  1. <!DOCTYPE html>
  2. <html lang="ru" itemscope itemtype="http://schema.org/WebPage">
  3. <head>
  4. <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  5. <meta charset="Windows-1251">
  6. <meta name="viewport" content="width=device-width, height=device-height">
  7. <title>Физика «на пальцах»: солверы физических движков / Физика / Статьи / Программирование игр / GameDev.ru — Разработка игр</title>
  8. <meta name="Description" content="Данная статья объясняет принципы работы солверов современных физических движков. В этом очерке я постараюсь объяснить, как работают солверы современных на момент написания статьи...">
  9. <meta name="Keywords" content="Физика, «на, пальцах», солверы, физических, движков, Физика, Статьи, Программирование, игр, GameDev, ru, Разработка, игр">
  10. <link href="https://gamedev.ru/favicon.ico" rel="SHORTCUT ICON">
  11. <link href="https://gamedev.ru/_css/main_1718324380.css" rel="stylesheet" type="text/css">
  12. <script src="https://gamedev.ru/_js/skif_1718666590.js"></script>
  13. <meta name="yandex-verification" content="7262e69d68dab0c9">
  14. <link rel="icon" type="image/png" href="https://gamedev.ru/apple-touch-icon-196x196.png" sizes="196x196">
  15. </head>
  16.  
  17. <body class="no-mathjax">
  18. <div id="tool">
  19.  <div class="bound unselectable">
  20.    <div class="scroll-icons">
  21.      <button type="button" data-script="Skif_Menu" class="tool"><div class="menutog"></div></button>
  22.        <div id="menu" data-visible="0"><div class="content">
  23.          
  24. <ul>
  25. <li><a href="https://gamedev.ru/news/" title="Новости в области разработки игр">Новости</a></li>
  26. <li class="sel"><a href="https://gamedev.ru/articles/" title="Статьи для разработчиков игр">Статьи</a>
  27.  <ul>
  28.   <li class="sel"><a href="https://gamedev.ru/code/articles/" title="Статьи по программированию игр">&gt; Код</a></li>
  29.   <li><a href="https://gamedev.ru/art/articles/" title="Графический дизайн">Арт</a></li>
  30.   <li><a href="https://gamedev.ru/gamedesign/articles/" title="Игровой дизайн">Дизайн</a></li>
  31.   <li><a href="https://gamedev.ru/industry/articles/" title="Игровая индустрия, события, интервью">Индустрия</a></li>
  32.  </ul>
  33. </li>
  34. <li><a href="https://gamedev.ru/tip/" title="Подсказки">Подсказки</a></li>
  35. <li><a href="https://gamedev.ru/terms/" title="Описание терминов в разработке игр">Термины</a></li>
  36. <li><a href="https://gamedev.ru/faq/" title="FAQ: Ответы на частозадаваемые вопросы">FAQ</a></li>
  37. <li><a href="https://gamedev.ru/pages/" title="Персональные страницы пользователей">Страницы</a></li>
  38. <li><a href="https://gamedev.ru/community/" title="Сообщества: Пользовательские разделы с группами участников">Сообщества</a></li>
  39. <li><a href="https://gamedev.ru/forum/" title="Форум разработчиков игр">Форум</a></li>
  40. <li><a href="https://gamedev.ru/files/" title="Качалка: Файлы и изображения">Качалка</a></li>
  41. <li><a href="https://gamedev.ru/members/" title="Участники проекта GameDev.ru">Участники</a></li>
  42. <li><a href="https://gamedev.ru/users/" title="Регистрации пользователей">Пользователи</a></li>
  43. </ul>
  44.  
  45.          <button type="button" class="flat grey" data-script="Skif_TogleStyle">Тёмная тема</button>
  46.        </div></div>
  47.      <a href="https://gamedev.ru/" class="tool"><span class="gamedev i x32 mobile"></span><span class="gamedev-logo i x32 not-mobile"></span></a>
  48.      <a href="https://gamedev.ru/pubs" class="tool"><span class="pubs i x32 mobile"></span><span class="tool not-mobile">Публикации</span></a>
  49.      <a href="https://gamedev.ru/projects/forum/" class="tool"><span class="projects i x32 mobile"></span><span class="tool not-mobile">Проекты</span></a>
  50.      <a href="https://gamedev.ru/forum/" class="tool"><span class="forum i x32 mobile"></span><span class="tool not-mobile">Форум</span></a>
  51.      <a href="https://gamedev.ru/job/forum/" class="tool"><span class="job i x32 mobile"></span><span class="tool not-mobile">Работа</span></a>
  52.      <button type="button" data-script="Skif_Search" class="tool"><div class="lupa"></div></span>
  53.    </div>
  54.    <div id="search"></div>
  55.  
  56.    <div id="enter-pos">
  57.      
  58.      <button type="button" class="volume green enter" data-script="but_Modal" data-init-script="but_ModalInit">Войти</button>
  59.      <div id="modal" class="veil"><div id="modal_pos" class="bound"><div class="modal-content">
  60.      <div id="login"></div>
  61.      </div></div></div>
  62.  
  63.    </div>
  64.  </div>
  65. </div>
  66.  
  67. <div class="path"><div class="bound"><span><a href="https://gamedev.ru/code/" title="Программирование игр">Программирование</a></span><span><a href="https://gamedev.ru/code/articles/" title="Статьи">Статьи</a></span><span><a href="https://gamedev.ru/code/articles/?physics" title="Физика">Физика</a></span></div></div>
  68.  
  69. <div class="bound">
  70. <article itemscope itemtype="http://schema.org/Article">
  71.  
  72. <h1 itemprop="name headline">Физика «на пальцах»: солверы физических движков</h1>
  73.  
  74. <link itemprop="mainEntityOfPage" href="https://gamedev.ru/code/articles/?id=4706">
  75. <p class="r">Автор: <b><a href="https://gamedev.ru/users/?id=20946" title="Suslik: Александр Санников"><span itemprop="author">Александр Санников</span></a></b></p>
  76.  
  77. <p class="mar">Данная статья объясняет принципы работы солверов современных физических движков.</p>
  78. <a id="cut"></a><p class="mar">В этом очерке я постараюсь объяснить, как работают <a href="https://gamedev.ru/code/terms/Solver" title="Солвер (Solver)">солверы</a> современных на момент написания статьи <a href="https://gamedev.ru/code/terms/PhysicsEngine" title="Физический движок (Physics Engine)">движков игровой физики</a>: <a href="https://gamedev.ru/code/terms/PhysX" title="PhysX">PhysX</a>, <a href="https://gamedev.ru/code/terms/ODE" title="ODE: Open Dynamics Engine">ODE</a>, Bullet, Box2D. За строгой математикой любопытный читатель всегда может обратиться к многочисленным существующим пейперам, <a href="https://gamedev.ru/community/gd_physcomm/articles/?id=682">ссылки</a> на которые можно найти в нашем сообществе и лекции, <a href="https://gamedev.ru/community/gd_physcomm/articles/">прочитанные</a> в нём же. Я же постараюсь абстрагироваться от строгой математики и попытаюсь объяснить, как это работает, что же означает каждое действие <i>солвера</i>. В любом случае, понимая, как он работает, проще вести и диагностику и расширение функциональности, например, разработать новый, специфический тип джойнта. Также рекомендую к прочтению более обзорную <a href="https://gamedev.ru/community/gd_physcomm/articles/phys_engine_development">статью о разработке своего импульсного физического движка</a>.</p>
  79.  
  80. <p><b><a href="https://gamedev.ru/code/articles/?id=4706#formulirovka_zadachi" title="Физика «на пальцах»: солверы физических движков: Формулировка задачи">Формулировка задачи</a></b><br>
  81. <b><a href="https://gamedev.ru/code/articles/?id=4706#opisanie_metoda_sequential_impul" title="Физика «на пальцах»: солверы физических движков: Описание метода Sequential Impulses">Описание метода Sequential Impulses</a></b><br>
  82. <b><a href="https://gamedev.ru/code/articles/?id=4706#warmstarting__goryachiy_start_" title="Физика «на пальцах»: солверы физических движков: Warmstarting (горячий старт)">Warmstarting (горячий старт)</a></b><br>
  83. <b><a href="https://gamedev.ru/code/articles/?id=4706#sluchay_uprugogo_soudareniya" title="Физика «на пальцах»: солверы физических движков: Случай упругого соударения">Случай упругого соударения</a></b><br>
  84. <b><a href="https://gamedev.ru/code/articles/?id=4706#usovershenstvovaniya_algoritma" title="Физика «на пальцах»: солверы физических движков: Усовершенствования алгоритма">Усовершенствования алгоритма</a></b><br>
  85. <b><a href="https://gamedev.ru/code/articles/?id=4706#pseudovelocities__psevdoskorosti" title="Физика «на пальцах»: солверы физических движков: Pseudovelocities (псевдоскорости)">Pseudovelocities (псевдоскорости)</a></b><br>
  86. <b><a href="https://gamedev.ru/code/articles/?id=4706&amp;page=2#drugie_tipi_soedineniy__djoyntov" title="Физика «на пальцах»: солверы физических движков: Другие типы соединений (джойнтов)">Другие типы соединений (джойнтов)</a></b><br>
  87. <b><a href="https://gamedev.ru/code/articles/?id=4706&amp;page=2#trenie" title="Физика «на пальцах»: солверы физических движков: Трение">Трение</a></b><br>
  88. <b><a href="https://gamedev.ru/code/articles/?id=4706&amp;page=2#zaklyuchenie" title="Физика «на пальцах»: солверы физических движков: Заключение">Заключение</a></b><br>
  89. <b><a href="https://gamedev.ru/code/articles/?id=4706&amp;page=2#kod" title="Физика «на пальцах»: солверы физических движков: Код">Код</a></b></p>
  90.  
  91. <h1 id="formulirovka_zadachi">Формулировка задачи</h1>
  92. <p class="mar">Прежде всего сформулируем в чём же состоит задача солвера. На входе у него есть особым образом заданные ограничения на движения тел, его задача состоит в том, чтобы воздействовать на тела таким образом, чтобы все эти ограничения не нарушались или нарушались как можно меньше.</p>
  93.  
  94. <p class="mar">К сожалению, солвер «понимает» далеко не любые типы ограничений, а только заданные особым образом — ограничивающие ровно одну степень свободы системы.</p>
  95.  
  96. <p class="mar">Вообще, количество степеней свободы системы — это минимальное количество действительных чисел, которым можно описать произвольное её мгновенное состояние. Например, для описания системы, состоящей из статического бокса достаточно шести чисел — трёх компонент вектора, определяющего положение его центра масс и ещё три — углы Эйлера, определяющие его ориентацию. Если система состоит из двух боксов, свободно находящихся в пространстве, степеней свободы у неё будет уже двенадцать — по шесть на каждый бокс. А если прислонить бокс одной гранью к бесконечной плоскости, степеней свободы будет всего три, так как его положение можно будет однозначно установить двумерными координатами точки на плоскости и ещё одну координату на угол поворота в этой же плоскости.</p>
  97.  
  98. <p class="mar">Так какие же степени свободы мы хотим ограничить, например, при столкновении двух тел? Рассмотрим простой случай, когда тела касаются в единственной точке <i>point</i>, тела взаимодействуют по нормали <i>norm</i> и «отскок» нулевой. Контакт будет считаться удовлетворённым, если проекция относительной скорости двух тел на нормаль контакта будет нулевой. Интуитивное объяснение - при столкновении тел их скорость меняется таким образом, что контактирующие точки перестают сближаться.</p>
  99.  
  100. <p class="mar">Рассмотрим на простом примере двух столкнувшихся шаров без учёта вращения:
  101. <br>
  102. <br>
  103. скорость первого шара: body1-&gt;velocity,
  104. <br>
  105. скорость второго шара: body2-&gt;velocity,
  106. <br>
  107. относительная скорость в точке contact-&gt;point можно рассмотреть, например, в системе отсчёта второго шара, она будет равна:</p>
  108. <div class="overflow pre"><pre>  <span class="comment">//все операции гораздо удобнее проводить над векторами,</span>
  109.  <span class="comment">//а не над их отдельными компонентами</span>
  110.  Vector3 relativeVelocity = body1-<span class="bracket">&gt;</span>velocity - body2-<span class="bracket">&gt;</span>velocity;</pre></div><p class="mar">а теперь просто выразим проекцию относительной скорости на нормаль контакта:</p>
  111. <div class="overflow pre"><pre>  <span class="comment">//* - скалярное произведение</span>
  112.  <span class="key">float</span> velocityProjection = relativeVelocity * contact-<span class="bracket">&gt;</span>norm;</pre></div><p class="mar">Теперь, если мы потребуем от солвера, чтобы velocityProjection было равно нулю, мы тем самым ограничим одну степень свободы системы — движение тел вдоль нормали контакта. Важно понять, что, удовлетворив этому требованию, мы лишь запретим контактирующим телам пролезать всё глубже и глубже друг в друга, никак не разрешая коллизию в случае, если они пересеклись. О том, как разрешать именно взаимопроникновения, мы рассмотрим в разделе &quot;Pseudovelocities&quot;, ниже.</p>
  113.  
  114. <p class="mar">Как же учесть угловую составляющую скорости? Из курса механики известно, что если тело вращается вокруг центра масс body-&gt;pos с угловой скоростью body-&gt;angularVelocity, то скорость его точки <i>point</i> можно выразить как</p>
  115. <div class="overflow pre"><pre>  <span class="comment">//^ - cross product, векторное произведение</span>
  116.  Vector3 pointVelocity =
  117.      body-<span class="bracket">&gt;</span>velocity + <span class="bracket">(<wbr></span>point - body-<span class="bracket">&gt;</span>pos<span class="bracket">)</span> ^ body-<span class="bracket">&gt;</span>angularVelocity;</pre></div><p class="mar">Таким образом распишем выражение для нашей ограниченной степени свободы с учётом угловой скорости:</p>
  118. <div class="overflow pre"><pre>  Vector3 relativeVelocity =
  119.      body1-<span class="bracket">&gt;</span>velocity + <span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>point - body1-<span class="bracket">&gt;</span>pos<span class="bracket">)</span> ^ body1-<span class="bracket">&gt;</span>angularVelocity
  120.    - body2-<span class="bracket">&gt;</span>velocity - <span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>point - body2-<span class="bracket">&gt;</span>pos<span class="bracket">)</span> ^ body2-<span class="bracket">&gt;</span>angularVelocity
  121.  <span class="key">float</span> velocityProjection = relativeVelocity * contact-<span class="bracket">&gt;</span>norm;
  122.  <span class="comment">//полагаем velocityProjection = 0</span></pre></div><p class="mar">Преобразуем, объединив в одно выражение:</p>
  123. <div class="overflow pre"><pre>  <span class="key">float</span> velocityProjection =
  124.      body1-<span class="bracket">&gt;</span>velocity * contact-<span class="bracket">&gt;</span>norm
  125.    + <span class="bracket">(<wbr></span><span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>point - body1-<span class="bracket">&gt;</span>pos<span class="bracket">)</span> ^ body1-<span class="bracket">&gt;</span>angularVelocity<span class="bracket">)</span> * contact-<span class="bracket">&gt;</span>norm
  126.    - body2-<span class="bracket">&gt;</span>velocity * contact-<span class="bracket">&gt;</span>norm
  127.    - <span class="bracket">(<wbr></span><span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>point - body2-<span class="bracket">&gt;</span>pos<span class="bracket">)</span> ^ body2-<span class="bracket">&gt;</span>angularVelocity<span class="bracket">)</span> * contact-<span class="bracket">&gt;</span>norm;
  128.  <span class="comment">//полагаем velocityProjection = 0</span></pre></div><p class="mar">Далее применяем нехитрое векторное преобразование (a ^ b) * c == (c ^ a) * b и ещё раз преобразуем формулу:</p>
  129. <div class="overflow pre"><pre>  <span class="key">float</span> velocityProjection =
  130.      contact-<span class="bracket">&gt;</span>norm * body1-<span class="bracket">&gt;</span>velocity
  131.    + <span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>norm ^ <span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>point - body1-<span class="bracket">&gt;</span>pos<span class="bracket">)</span><span class="bracket">)</span> * body1-<span class="bracket">&gt;</span>angularVelocity
  132.    - contact-<span class="bracket">&gt;</span>norm * body2-<span class="bracket">&gt;</span>velocity
  133.    - <span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>norm ^ <span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>point - body2-<span class="bracket">&gt;</span>pos<span class="bracket">)</span><span class="bracket">)</span> * body2-<span class="bracket">&gt;</span>angularVelocity;
  134.    <span class="comment">//полагаем velocityProjection = 0</span></pre></div><p class="mar">и замечаем, что эту формулу можно переписать в виде:</p>
  135. <div class="overflow pre"><pre>  Vector3 n1 = contact-<span class="bracket">&gt;</span>norm;
  136.  Vector3 w1 =  <span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>norm ^ <span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>point - body1-<span class="bracket">&gt;</span>pos<span class="bracket">)</span><span class="bracket">)</span>;
  137.  Vector3 n2 = -contact-<span class="bracket">&gt;</span>norm;
  138.  Vector3 w2 = -<span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>norm ^ <span class="bracket">(<wbr></span>contact-<span class="bracket">&gt;</span>point - body2-<span class="bracket">&gt;</span>pos<span class="bracket">)</span><span class="bracket">)</span>;
  139.  <span class="key">float</span> velocityProjection =
  140.      n1 * body1-<span class="bracket">&gt;</span>velocity + w1 * body1-<span class="bracket">&gt;</span>angularVelocity
  141.    + n2 * body2-<span class="bracket">&gt;</span>velocity + w2 * body2-<span class="bracket">&gt;</span>angularVelocity;</pre></div><p class="mar">То есть получается, что ограничение, вносимое в систему из двух тел контактом, можно описать четырьмя векторами: n1, w1, n2, w2. справедливости ради заметим, что записанные подряд координаты этих самых векторов образуют строку Той Самой Матрицы Якоби, или Якобиана </p>
  142.  
  143. <p class="mar">j = (n1, w1, n2, w2),</p>
  144.  
  145. <p class="mar">а ограничение можно описать как j * v = 0, где v - это вектор обобщённых скоростей:</p>
  146.  
  147. <p class="mar">v = (body1-&gt;velocity, body1-&gt;angularVelocity, body2-&gt;velocity, body2-&gt;angularVelocity)</p>
  148.  
  149. <h1 id="opisanie_metoda_sequential_impul">Описание метода Sequential Impulses</h1>
  150. <p class="mar">Осталось всего-то ничего, понять, что нужно сделать с телами, чтобы этому самому velocityProjection = 0 удовлетворить. Другими словами - что должно произойти с контактирующими телами, чтобы они перестали друг в друга проникать? Продолжаем рассуждать из физических соображений:</p>
  151.  
  152. <p class="mar">Взаимодействие столкнувшихся тел осуществляется передачей импульса в направлении contact-&gt;norm некоторой амплитуды <i>lambda</i> к точке contact-&gt;point к первому телу и такого же импульса противоположенного направления второму телу. Другими словами, тела при столкновении обмениваются имульсом contact-&gt;norm * lambda. Работу солвера в случае одного контакта можно свести к нахождению этой самой лямбды.</p>
  153.  
  154. <p class="mar">Ещё немного книжной механики. Распишем, как изменится линейная и угловая скорость тела, если приложить к его точке <i>point</i> импульс в направлении <i>norm</i> и с модулем <i>lambda</i>:
  155. <br>
  156. <br>
  157. линейная составляющая высчитывается просто: делением вектора импульса на массу тела. Масса каждого тела для удобства хранится инвертированная, body-&gt;invMass = 1.0f / body-&gt;mass;</p>
  158. <div class="overflow pre"><pre>  Vector3 velocityAfterCollision =
  159.      body-<span class="bracket">&gt;</span>velocity + norm * lambda * body-<span class="bracket">&gt;</span>invMass;</pre></div><p class="mar">С угловой составляющей чуть сложнее. Для простоты положим, что эллипсоид инерции симметричен, выражен в шар, то есть задаётся единственным числом — моментом инерции вокруг произвольной оси, проходящей через центр масс тела. Нехилое допущение, но желающие без труда могут преобразовать формулу для произвольного тензора инерции.
  160. <br>
  161. Говоря простым языком, момент инерции в нашем случае - число, полностью аналогичное массе, только масса связывает силу с ускорением, а момент инерции связывает момент силы с угловым ускорением. Для тестовых расчётов момент инерции вполне можно брать порядка массы объекта, помноженной на квадрат его характерного размера(т.е. длины или, например, высоты). Если ошибиться в расчёте момента инерции раз в 10, то физика работать, просто несколько менее естественно, а неподготовленный глаз может и не заметить отличий. Момент инерции, как и в случае с массой, храним инвертированным: body-&gt;invInertia = 1.0f / body-&gt;inertia;</p>
  162. <div class="overflow pre"><pre>  Vector3 angularVelocityAfterCollision = body-<span class="bracket">&gt;</span>angularVelocity
  163.        + <span class="bracket">(<wbr></span><span class="bracket">(<wbr></span>norm * lambda<span class="bracket">)</span> ^ <span class="bracket">(<wbr></span>point - body-<span class="bracket">&gt;</span>pos<span class="bracket">)</span><span class="bracket">)</span> * body-<span class="bracket">&gt;</span>invInertia;</pre></div><p class="mar">Почему мы храним инвертированную массу и инвертированный момент инерции? На самом деле просто для удобства задания бесконечных масс, если body-&gt;mass устремить в бесконечность, то body-&gt;invMass будет равна нулю. Поэтому вместо обработки бесконечных величин мы просто храним обратные, которые в этом случае равны нулю. </p>
  164.  
  165. <p class="mar">Что же замечаем? Изменения линейной и угловой скоростей можно выразить через те самые n1, w1, n2, w2:</p>
  166. <div class="overflow pre"><pre>  <span class="key">float</span> velocityProjection =
  167.      n1 * <span class="bracket">(<wbr></span>body1-<span class="bracket">&gt;</span>velocity + n1 * body1-<span class="bracket">&gt;</span>invMass * lambda<span class="bracket">)</span>
  168.    + w1 * <span class="bracket">(<wbr></span>body1-<span class="bracket">&gt;</span>angularVelocity + w1 * body1-<span class="bracket">&gt;</span>InvInertia * lambda<span class="bracket">)</span>
  169.    + n2 * <span class="bracket">(<wbr></span>body2-<span class="bracket">&gt;</span>velocity + n2 * body2-<span class="bracket">&gt;</span>invMass * lambda<span class="bracket">)</span>
  170.    + w2 * <span class="bracket">(<wbr></span>body2-<span class="bracket">&gt;</span>angularVelocity + w2 * body2-<span class="bracket">&gt;</span>InvInertia * lambda<span class="bracket">)</span>;</pre></div><p class="mar">Преобразуем, получаем:</p>
  171. <div class="overflow pre"><pre><span class="key">float</span> velocityProjection = n1 * body1-<span class="bracket">&gt;</span>velocity + n2 * body2-<span class="bracket">&gt;</span>velocity
  172.        + w1 * body1-<span class="bracket">&gt;</span>angularVelocity + w2 * body2-<span class="bracket">&gt;</span>angularVelocity
  173.        + <span class="bracket">(<wbr></span>n1 * n1 * body1-<span class="bracket">&gt;</span>invMass  + w1 * w1 * body1-<span class="bracket">&gt;</span>InvInertia
  174.        + n2 * n2 * body2-<span class="bracket">&gt;</span>invMass  + w2 * w2 * body2-<span class="bracket">&gt;</span>InvInertia<span class="bracket">)</span> * lambda;</pre></div><p class="mar">Напомню, что контакт считается решённым, если velocityProjection обращается в ноль. Получаем легко решающееся уравнение:</p>
  175. <div class="overflow pre"><pre>  <span class="key">float</span> a = n1 * body1-<span class="bracket">&gt;</span>velocity + n2 * body2-<span class="bracket">&gt;</span>velocity
  176.    + w1 * body1-<span class="bracket">&gt;</span>angularVelocity
  177.    + w2 * body2-<span class="bracket">&gt;</span>angularVelocity;
  178.  
  179.  <span class="key">float</span> b = <span class="bracket">(<wbr></span>n1 * n1 * body1-<span class="bracket">&gt;</span>invMass
  180.    + w1 * w1 * body1-<span class="bracket">&gt;</span>InvInertia
  181.    + n2 * n2 * body2-<span class="bracket">&gt;</span>invMass  
  182.    + w2 * w2 * body2-<span class="bracket">&gt;</span>InvInertia<span class="bracket">)</span>;
  183.  
  184.  a + b * lambda = <span class="digit">0</span>;</pre></div><p class="mar">Уравнение такое можно решить, очевидно: lambda = –a / b; таким образом мы нашли имульс, которым обмениваются тела при введении ограничения степени свободы (n1, w1, n2, w2). Важно отметить, что сила реакции опоры может быть направлена только от поверхности контакта, то есть никак не вовнутрь. Таким образом, если lambda получается отрицательной, это значит, что тела не расталкиваются, а слипаются. Этого допустить никак нельзя, и если в каком-то контактном джойнте(и вообще любом, где телам разрешено передавать импульс только в одном направлении), импульс(lambda) получается отрицательным, мы его просто обнуляем. Далее, применив этот импульс к обоим телам, находим корректированные скорости после контакта.</p>
  185.  
  186. <p class="mar">Если перед решением джойнта мы замечаем, что проекция относительной скорости тел в точке контакта на нормаль этого контакта уже положительная(физический смысл - тела разлетаются), то контакт заведомо можно пропустить, так как он уже считается решённым.</p>
  187.  
  188. <p class="mar">Вот, собственно, и все чудеса, такая нехитрая последовательность действий позволяет солверу удовлетворить одному ограничению степени свободы. В чём же тогда проблема? Не слишком-то это было трудно. А проблема, как нетрудно догадаться, в том, что ограничений таких мноого (по меньшей мере по одному на каждый контакт) и удовлетворить им нужно всем <i>одновременно</i>. Чтобы сделать это, нужно каким-то образом решить систему уравнений, состоящую из количества уравнений, равного количеству ограниченных степеней свободы. Обычно эта величина порядка нескольких тысяч. Большинство методов решения системы линейных уравнений работает за сложность порядка O(n<sup>3</sup>), что просто убило бы производительность, прямой подход «в лоб» применяется редко. Зато существуют итеративные методы, постепенно делающие решение всё более и более точным. Так получилось, что если решать полученную систему уравнений одним из таких методов, называемых Projected Gauss-Seidel Solver, PGS, то последовательность производимых при этом действий математически эквивалентна последовательному решению джойнтов, буквально как описывалось выше. Этот подход широко распространён и называется Sequential Impulses.</p>
  189.  
  190. <p class="mar">То есть, если мы будем просто решать ограниечение за ограничением, не обращая внимание, что оно, вообще говоря, может быть и не одно и будем делать это достаточно долго, то полученный результат будет сходиться к результату, как если бы их решали в системе. За строгими доказательствами — в другие источники, но, уж на слово поверьте, там действительно ничего слишком сложного.</p>
  191.  
  192. <h1 id="warmstarting__goryachiy_start_">Warmstarting (горячий старт)</h1>
  193. <p class="mar">Существует множество эвристик, существенно улучшающих сходимость алгоритма Sequential Impulses. Одна из самых важных называется warmstarting, заключается в том, что мы ищем решение системы уравнений каждую итерацию процесса моделирования не с нуля, а используя результаты с предыдущего шага. Эвристика использует свойство так называемой временной когерентности (time coherence) — предполагается, что напряжения, действующие в ограничениях, меняются от кадра к кадру не слишком значительно.</p>
  194.  
  195. <p class="mar">На практике реализуется так:<ul><li>сначала применяем аккумулированные на прошлой итерации импульсы во всех ограничениях</li><li>продолжаем решать ограничения, как ни в чём не бывало находя импульсы <i>lambda</i>, и просто прибавляем их к аккумулированному значению.</li></ul></p>
  196.  
  197. <p class="mar">при этом также немного меняется стратегия, по которой мы накладываем ограничения на величину передаваемого импульса. если в случае без warmstarting'а ограничение на то, что тела могут только разлетаться, не слипаясь, схематично выглядело так:</p>
  198. <div class="overflow pre"><pre>lambda = ComputeLambda<span class="bracket">(<wbr></span><span class="bracket">)</span>;
  199. <span class="key">if</span><span class="bracket">(<wbr></span>lambda <span class="bracket">&lt;</span> <span class="digit">0</span>.<span class="digit">0</span>f<span class="bracket">)</span> <span class="key">return</span>;
  200. ApplyImpulse<span class="bracket">(<wbr></span>lamda<span class="bracket">)</span>;</pre></div>
  201. <p class="mar">то с добавлением аккумулирования импульса, стратегия немного меняется:</p>
  202. <div class="overflow pre"><pre>lambda = ComputeLambda<span class="bracket">(<wbr></span><span class="bracket">)</span>;
  203. accumulatedImpulse += lambda.
  204. <span class="key">if</span><span class="bracket">(<wbr></span>accumulatedImpulse <span class="bracket">&lt;</span> <span class="digit">0</span>.<span class="digit">0</span>f<span class="bracket">)</span>
  205. {
  206.  lambda += <span class="bracket">(<wbr></span><span class="digit">0</span>.<span class="digit">0</span>f - accumulatedImpulse<span class="bracket">)</span>;
  207.  accumulatedImpulse = <span class="digit">0</span>.<span class="digit">0</span>f;
  208. }
  209. ApplyImpulse<span class="bracket">(<wbr></span>lamda<span class="bracket">)</span>;</pre></div>
  210. <p class="mar">Физический смысл произошедшего в том, что мы теперь ограничиваем только накопленный импульс, а lambda считаем его изменением. и если мы видим, что какое-то изменение lambda сделало накопленный импульс отрицательным, мы урезаем это изменение таким образом, чтобы оно обращало накопленный импульс ровно в ноль.</p>
  211.  
  212. <p class="mar">Основная проблема такого подхода — определить, какой из контактов был и на прошлой итерации процесса моделирования, а какой появился только что. Выдвигаются особые требования к алгоритму определения столкновений и его косвенная связь с, казалось бы, независимым от него солвером.</p>
  213.  
  214. <h1 id="sluchay_uprugogo_soudareniya">Случай упругого соударения</h1>
  215. <br>
  216. До этого момента рассматривался случай, когда контакт считался решённым, если относительная скорость тел в проекции на нормаль контакта равняется нулю, то есть, другими словами, случай неупругого соударения. Если представить себе два шарика, летящие навстречу друг другу, то после решения такого типа контакта, они будто слипаются. Алгоритм нетрудно модифицировать на случай упругого соударения. Введём коэффициент отскока bounce, который принимает значения от 0 до 1, при этом случаю абсолютно неупругого соударения соответствует bounce = 0, случаю абсолютно упругого - bounce = 1.
  217. <p class="mar">Далее вспомним, чему будет равняться скорость, если тела обменяются через данный контакт импульсом с модулем lambda:</p>
  218. <div class="overflow pre"><pre>  <span class="key">float</span> velocityProjection =
  219.      n1 * <span class="bracket">(<wbr></span>body1-<span class="bracket">&gt;</span>velocity + n1 * body1-<span class="bracket">&gt;</span>invMass * lambda<span class="bracket">)</span>
  220.    + w1 * <span class="bracket">(<wbr></span>body1-<span class="bracket">&gt;</span>angularVelocity + w1 * body1-<span class="bracket">&gt;</span>InvInertia * lambda<span class="bracket">)</span>
  221.    + n2 * <span class="bracket">(<wbr></span>body2-<span class="bracket">&gt;</span>velocity + n2 * body2-<span class="bracket">&gt;</span>invMass * lambda<span class="bracket">)</span>
  222.    + w2 * <span class="bracket">(<wbr></span>body2-<span class="bracket">&gt;</span>angularVelocity + w2 * body2-<span class="bracket">&gt;</span>InvInertia * lambda<span class="bracket">)</span>;</pre></div><p class="mar">Если в случае с абсолютно неупругим соударением мы ставили условие velocityProjection &gt;= 0. Теперь поставим условие следующим образом: velocityProjection = -bounce * initialVelocityProjection. Здесь initialVelocityProjection - величина, равная:</p>
  223. <div class="overflow pre"><pre>  <span class="key">float</span> initialVelocityProjection =
  224.      n1 * body1-<span class="bracket">&gt;</span>velocity
  225.    + w1 * body1-<span class="bracket">&gt;</span>angularVelocity
  226.    + n2 * body2-<span class="bracket">&gt;</span>velocity
  227.    + w2 * body2-<span class="bracket">&gt;</span>angularVelocity;</pre></div>
  228. <p class="mar">Чтобы понять, что это значит, вернёмся к аналогии с двумя сталкивающимися шариками. То есть мы хотим, чтобы при bounce = 1 тела после решения контакта начали бы отдаляться с такой же скоростью, с какой они до его решения сближались, а при bounce = 0, чтобы они относительно друг друга остановились. В остальном алгоритм решения каждого контакта остаётся практически без изменений:
  229. <br>
  230. 1) Считаем величину b, которая является коэффициентом при lambda и зависит от нормали, точки контакта, массы и момента инерции каждого из тел. Формула для неё остаётся той же самой.
  231. <br>
  232. 2) Считаем велиичну a, которая зависит от скоростей контактирующих тел и всё тех же параметров контакта. Теперь, когда мы ввели коэффициент bounce, принимает такой вид:</p>
  233. <div class="overflow pre"><pre>  <span class="key">float</span> a = <span class="bracket">(<wbr></span>n1 * body1-<span class="bracket">&gt;</span>velocity + n2 * body2-<span class="bracket">&gt;</span>velocity
  234.    + w1 * body1-<span class="bracket">&gt;</span>angularVelocity
  235.    + w2 * body2-<span class="bracket">&gt;</span>angularVelocity<span class="bracket">)</span> + bounce * initialVelocityProjection;</pre></div>
  236. <p class="mar">Выражение для initialVelocityProjecton - выше.
  237. <br>
  238. 3) находим lambda = -a / b;
  239. <br>
  240. 4) применяем lambda к обоим телам.</p>
  241.  
  242. <p class="mar">Важно отметить, что если используется несколько итераций солвера, то математически корректно будет сначала посчитать для каждого контакта initialVelocityProjection, а потом прогонять несколько итераций солвера, не изменяя это значение. </p>
  243.  
  244. <h1 id="usovershenstvovaniya_algoritma">Усовершенствования алгоритма</h1>
  245. <p class="mar">Приведённая последовательность действий лишь частично предотвратит взаимопроникновение тел. К сожалению, из-за ошибок интегрирования и итеративного не совсем точного решения системы, тела всё-таки будут частично проникать друг в друга, это может быть особенно заметно, если на тела продолжительно действует сближающая их сила. Простейший случай: бокс, стоящий на столе, при таком подходе будет постепенно в него погружаться. Чтобы предотвратить это явление, существует ряд методик. Одна из самых простых называется <i>velocity-based</i>, основана на том, что мы считаем контакт решённым не при условии velocityProjection &gt;= c а при модифицированном: velocityProjection &gt;= c + contact-&gt;depth * ERP.</p>
  246.  
  247. <p class="mar"><a href="https://gamedev.ru/code/terms/ERP" title="ERP: Error Reduction Parameter">ERP</a> (error reduction parameter) играет роль чего-то вроде архимедовой силы — чем глубже тела проникают друг в друга, тем быстрее мы хотим, чтобы они искусственно расталкивались. физическая аналогия: что-то в духе архимедовой силы, выталкивающей пенопласт из воды. Грубоватая, правда, аналогия, потому что, во-первых, архимедова сила зависит от объёма погружённой в жидкость части тела (мы учитываем глубину), и архимедова сила влияет на ускорение, а мы воздействуем напрямую на скорость.</p>
  248.  
  249. <p class="mar">К сожалению, это простой, но не слишком эффективный подход, из-за него глубоко проникшее тело «выплёвывается» из того, в которое оно проникло и может неестественно далеко улететь. Куда более практичный подход — использование так называемых псевдоимпульсов и псевдоскоростей:</p>
  250.  
  251. <h1 id="pseudovelocities__psevdoskorosti">Pseudovelocities (псевдоскорости)</h1>
  252. <p class="mar"><b>Псевдоскорость</b> — искусственно введённый в систему параметр, служащий для расталкивания тел. В случае контакта, вместо использования одного уравнения мы поступаем следующим образом:
  253. <br>
  254. <br>
  255. 1) составляем как описывалось выше систему контактов без velocity-based добавки:
  256. <br>
  257. velocityProjection &gt;= c;
  258. <br>
  259. 2) решаем, как ни в чём не бывало.
  260. <br>
  261. 3) обнуляем псевдоскорости всех тел.
  262. <br>
  263. 4) составляем ещё одну систему, на этот раз не для скоростей, а для псевдоскоростей:
  264. <br>
  265. pseudoVelocityProjection &gt;= contact-&gt;depth * ERP;
  266. <br>
  267. 5) решаем в точности таким же подходом систему для псевдоскоростей, предполагая, что это настоящие скорости тел.
  268. <br>
  269. 6) интегрируем движение каждого тела для обычных скоростей по закону
  270. <br>
  271. velocity += acceleration * timeStep; pos += velocity * timeStep;
  272. <br>
  273. 7) интегрируем движение каждого тела для псевдоскоростей по закону
  274. <br>
  275. pos += pseudoVelocity * timeStep;</p>
  276.  
  277. <p class="mar">Физический смысл подхода не слишком прозрачен, но состоит примерно в том, что мы наделяем тела дополнительными мгновенными скоростями, которые обнуляются, как только тела расталкиваются друг из друга. В результате чего они раздвигаются, но не разлетаются. Вообще говоря, они обнуляются каждый кадр, а не только в момент, когда одно тело вытолкнется из другого, но сути это не меняет. Хочется отметить, что попытки ввести в систему для псевдоскоростей <i>warmstarting</i> добром редко увенчиваются, потому что система и без него сходится достаточно быстро, а стабильность <i>warmstarting</i> понижает значительно. Это обозначает, что warmastarting имеет смысл применять для обычной системы импульсов, а систему для псевдоскоростей решать каждый кадр заново, без warmstarting'а.</p>
  278. <div class="pages">Страницы: <span>1</span> <a href="https://gamedev.ru/code/articles/?id=4706&amp;page=2">2</a> <a href="https://gamedev.ru/code/articles/?id=4706&amp;page=2">Следующая &raquo;</a></div>
  279.  
  280. <p> <a href="https://gamedev.ru/code/articles/tags/gauss-seidel">#gauss-seidel</a>, <a href="https://gamedev.ru/code/articles/tags/pgs">#pgs</a>, <a href="https://gamedev.ru/code/articles/tags/основы">#основы</a>, <a href="https://gamedev.ru/code/articles/tags/солверы">#солверы</a></p>
  281. <p class="date"><meta itemprop="datePublished" content="2010-01-5">5 января 2010
  282. (Обновление: 9 авг 2020)</p>
  283. <p><a href="https://gamedev.ru/code/forum/?id=127872">Комментарии</a> [<a href="https://gamedev.ru/code/forum/?id=127872&amp;page=43#m644">644</a>]</p>
  284.  
  285. <div itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="GameDev.ru" /></div>
  286. </article>
  287. <div class="seo"><script type="text/javascript">
  288. <!--
  289. var _acic={dataProvider:10};(function(){var e=document.createElement("script");e.type="text/javascript";e.async=true;e.src="https://www.acint.net/aci.js";var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t)})()
  290. //-->
  291. </script><style type='text/css'> .block_links * { background:transparent none repeat scroll 0 0 !important; border:medium none !important; clear:none !important; clip:rect(auto, auto, auto, auto) !important; font-size:100% !important; font-style:normal !important; font-variant:normal !important; font-weight:normal !important; height:auto !important; letter-spacing:normal !important; line-height:normal !important; margin:0 !important; overflow:visible !important; padding:0 !important; position:static !important; text-align:left !important; text-decoration:none !important; text-indent:0 !important; text-transform:none !important; vertical-align:baseline !important; visibility:visible !important; white-space:normal !important; width:auto; word-spacing:normal !important; z-index:auto !important; cursor: pointer!important; word-wrap: break-word!important; } .block_links li { display:list-item !important; list-style-image:none !important; list-style-position:outside !important; list-style-type:none !important; display: inline; } .block_links li DIV { padding:0.5em !important; } .block_links DIV, .block_links TABLE { padding: 5px !important; } .block_links { width: auto!important; font-family: Verdana!important; font-size: 11px!important; border: 1px solid #DDDDDD!important; background-color: #dedede!important; padding:5px!important; position: relative!important; display:block!important; -webkit-border-radius:5px !important; -khtml-border-radius:5px !important; -moz-border-radius:5px !important; border-radius:5px !important; } .block_links_icnt, .block_links_icnt * { text-align: left!important; } .block_links_text, .block_links_text A, .block_links_text A:hover { color: #808080!important; font-size: 11px!important; text-decoration: none!important; } .block_links_url { color: #808080!important; font-size: 11px!important; text-decoration: none!important; } .block_links_header, .block_links_header A { color: #808080!important; font-size: 13px!important; font-weight: bold!important; text-decoration: underline!important; } .block_links_sign { color: #999999!important; font-size: 10px!important; text-align: left!important; text-decoration: none!important; } .block_links_clear { clear:both!important; } .block_links TABLE { width: 100%!important; border: 0px!important; } </style><div class='block_links'  > <table> <tr> <td class='block_links_icnt' style='width:100%'> <span onclick='window.open(&#39;http://droid-mobile.ru/&#39;, &#39;_blank&#39;); return false;'><p class='block_links_header'> Скачать игры и моды на Андроид </p> <p class='block_links_text'> <a href="http://droid-mobile.ru/" target="_blank">Скачать игры и моды на Андроид</a> </p><p class='block_links_url'> droid-mobile.ru </p></span> </td> </tr> </table></div></div>
  292. </div>
  293.  
  294. <div class="footer"><div class="bound">
  295.  
  296. <div class="footblock r right text">
  297.  <button type="button" data-link="https://vk.com/gamedev_ru" title="vk.com"><div class="vk"></button>
  298.  <button type="button" data-link="https://twitter.com/ru_gamedev" title="twitter"><div class="twitter"></button>
  299.  <div style="padding-top: 15px;">
  300.    <a href="https://gamedev.ru/members/" class="gray">Участники</a>
  301.    <a class="g" href="https://gamedev.ru/help/donate" class="gray">Donate!</a>
  302.  </div>
  303. </div>
  304.  
  305. <div class="footblock text">
  306. <a href="https://gamedev.ru/?info" class="gray">Контакт</a>
  307. <a href="https://gamedev.ru/community/" class="gray">Сообщества</a>
  308. <a href="https://gamedev.ru/top/" class="gray">Каталог сайтов</a>
  309. <a href="https://gamedev.ru/tags/" class="gray">Категории</a>
  310. <a href="https://gamedev.ru/news/?adoc=arch" class="gray">Архив новостей</a>
  311. </div>
  312.  
  313.  <div class="center q"><strong>GameDev.ru — Разработка игр</strong><br>&copy;2001—2024</div>
  314.  
  315. </div></div>
  316.  
  317. <script><!--
  318. skif.run();
  319.  
  320. //-->
  321. </script>
  322. </body>
  323. </html>
  324.  
Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda