Developer
4,0
рейтинг
1 октября 2014 в 00:07

Разработка → Взрывы в Box2D

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

Мы расмотрим три вида взрывов разной сложности:
  • Нахождение тел в радиусе взрыва
  • Raycast – нахождения тел в радиусе лучей
  • Частицы – распространение многих маленьких тел от эпицентра взрыва


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



Применение импульса


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

Все описанное выше можно выразить следующим кодом:
void applyBlastImpulse(b2Body* body, b2Vec2 blastCenter, b2Vec2 applyPoint, float blastPower)
{
      b2Vec2 blastDir = applyPoint - blastCenter;
      float distance = blastDir.Normalize();
      // Игнорирование тел, которые находятся в центре взрыва
      if ( distance == 0 )
          return;

      float invDistance = 1 / distance;
      float impulseMag = blastPower * invDistance * invDistance;
      body->ApplyLinearImpulse( impulseMag * blastDir, applyPoint );
}


Нахождение тел в радиусе взрыва


Самый простой метод реализации взрывов: найти все тела внутри определенного радиуса взрыва относительно его центра. Немного уточнения: нам нужны тела с их центрами массы в пределах указанного диапазона взрыва. Для этих целей в Box2D есть метод QueryAABB:

MyQueryCallback queryCallback;
b2AABB aabb;
aabb.lowerBound = center - b2Vec2( blastRadius, blastRadius );
aabb.upperBound = center + b2Vec2( blastRadius, blastRadius );
m_world->QueryAABB( &queryCallback, aabb );
  
// Посмотреть все найденные тела и выбрать только те, у которых центр масс входит в радиус взрыва
for (int i = 0; i < queryCallback.foundBodies.size(); i++)
{
      b2Body* body = queryCallback.foundBodies[i];
      b2Vec2 bodyCom = body->GetWorldCenter();
      
      //ignore bodies outside the blast range
      if ( (bodyCom - center).Length() >= m_blastRadius )
          continue;
          
      applyBlastImpulse(body, center, bodyCom, blastPower );
}


Давайте посмотрим на результат от такой реализации взрыва. На картинке указаны тела, которые получат импульс после взрыва:


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

Объекты по обеим сторонам от центра взрыва имеют одинаковую массу, но тела справа получат импульс в 4 раза больше, чем тело слева.

Raycast метод


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

for (int i = 0; i < numRays; i++)
{
      float angle = (i / (float)numRays) * 360 * DEGTORAD;
      b2Vec2 rayDir( sinf(angle), cosf(angle) );
      b2Vec2 rayEnd = center + blastRadius * rayDir;
  
      RayCastClosestCallback callback;
      m_world->RayCast(&callback, center, rayEnd);
      if ( callback.m_body ) 
          applyBlastImpulse(callback.body, center, callback.point, (m_blastPower / (float)numRays));
}


Обратите внимание, мы делим силу взрыва на количество лучей. Сделано это для того чтобы было легче подобрать количество лучей без изменения общей силы импульса взрыва. Давайте посмотрим на взрыв с использованием 32 лучей:

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

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

Метод частиц


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

for (int i = 0; i < numRays; i++)
{
      float angle = (i / (float)numRays) * 360 * DEGTORAD;
      b2Vec2 rayDir( sinf(angle), cosf(angle) );
  
      b2BodyDef bd;
      bd.type = b2_dynamicBody;
      bd.fixedRotation = true; // Вращение необязательное
      bd.bullet = true;
      bd.linearDamping = 10;
      bd.gravityScale = 0; // Игнорирвать гравитацию
      bd.position = center; // Начальная точка в центре взрыва
      bd.linearVelocity = blastPower * rayDir;
      b2Body* body = m_world->CreateBody( &bd );
  
      b2CircleShape circleShape;
      circleShape.m_radius = 0.05; // Очень маленький радиус для тела
  
      b2FixtureDef fd;
      fd.shape = &circleShape;
      fd.density = 60 / (float)numRays;
      fd.friction = 0; // Трение необязательно
      fd.restitution = 0.99f; // Отражение от тел
      fd.filter.groupIndex = -1; // Частицы не должны сталкиваться друг с другом
      body->CreateFixture( &fd );
}

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


Вполне очевидно, что этот метод имеет все плюсы raycast-метода. Теперь у нас есть настоящая взрывная волна, которая отражается от тел, позволяя энергии взрыва правильно проходить препятствия.

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


Еще один побочный эффект: сила взрыва не применяется одновременно на все тела. Распространение частиц занимает некоторое время.


Автор оригинала (англ) — мой хороший знакомый iforce2d, автор физического редактора R.U.B.E. для Box2D.
Max Frai @Ockonal
карма
54,0
рейтинг 4,0
Developer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (21)

  • +10
    Была же точно такая статья в 2013г.
    • +8
      Да, моя статья. По неизвестным причинам попала в черновики, при попытке вернуть обратно — просто исчезла. Тема интересная же, думаю еще пару подобных экспериментов с R.U.B.E. опубликовать. В том числе физику танка.
      • +2
        В этом плане не понимаю Хабр, когда удаляют статью типа за рекламу продукта (особенно, если не своего). Да на хабре почти нет статей, которые по какой-либо причине нельзя отнести к рекламе
  • +1
    В последнем методе — не будут ли сверхбыстрые мелкие объекты проходить через очень тонкие стены?
    • +2
      Если использовать «Continuous Collision Detection», то нет. Как раз поле IsBullet у частички взрыва за это отвечает.
      • 0
        А где такая опция?

        Кстати, сам UniTech советует RayCast, чтобы быстрые пули не пролетали сквозь тонкие стены незамеченными.
        Я так понял, что на каждом Update надобно бить лучом от предыдущего кадра до нынешнего.
        • 0
          Mea culpa. Спутал движки. Но принцип вопроса остался — проверка лучом надёжнее, вроде бы?
          • 0
            Надежнее. Но для достижения наиболее впечатляющего эффекта луч придется перезапускать при пересечении с геометрией несколько раз для симуляции отражения ударной волны. Но гараздо большая проблема в том, что луч «движется» с бесконечной скоростью и при его использовании придется еще как-то симулировать скорость распространения ударной волны.
            С частицами проще: просто не стоит придавать им слишком большую скорость и включить CCD, как это посоветовали выше.
  • +4
    Еще один побочный эффект: сила взрыва не применяется одновременно на все тела.

    А так ли это плохо? В реальном мире, насколько я помню, взрывная волна тоже не мгновенно «долетает» до цели, а распространяется с какой-то скоростью.
    • 0
      В оригинале статьи как раз и говорится, что это «хороший побочный эффект»:
      Another nice side-effect is that the energy from the blast does not affect everything instantaneously — it takes a tiny bit longer for the particles to reach objects that are further from the blast center.

      ЗЫ. Если не ошибаюсь, в английском языке «side-effect» не имеет негативного оттенка, в отличие от русского.
  • 0
    А в методе частиц не будет ли взрыв сильно зависеть от физических свойств окружающий объектов, а именно от коэффициента отражения? Никогда подобный метод не пробовал, в основном работаю с мобильными платформами, но он кажется очень интересным, а телефоны становятся все производительнее)
  • 0
    Ещё, как недостаток всех методов (с точки зрения физики), можно отметить отсутствие поглощения частиц и соответственно отсутствие вибраций окружающих объектов. Конечно очевидно, что это будет доп. нагрузка на ЦП, но всё же было бы круто!
    • 0
      После столкновения с нестатичным объектом частицу можно прибить. Но в этом случае не будет переотражения взрывной волны от динамических объектов. Можно, если движок позволяет, назначать частицам максимальное количество переотражений, после которых они уничтожатся. Тогда не придется проверять, статичен ли объект.

      PS: возможно ли контролировать / обрабатывать колизии частиц в случае с Box2D я не знаю. Просто возможную идею изложил.
      • 0
        Мне глючится, что вы путаете фугасность с «осколочностью».

        Наблюдаемый импульс даёт ударная волна, а осколки ломают структуру объекта.
        То есть волна корёжит взаимное расположение частей танка и отбрасывает его целиком, а осколок пробивает двигатель и командира.

        А для моделирования фугасной ударной волны потребуется «ярд» частиц.

        В целом идея любопытная, но… Для кое-чего совсем не подходит.

        P.S. Да я и сам путаю фугасность с бризантностью. Кажется бризантность крошит предмет, а фугасность отбрасывает…
        • 0
          Ноги растут из описанного в статье способа эмуляции взрывной волны шрапнелью.
      • 0
        Возможно. Нужно назначить свой обработчик коллизий и для частиц применять необходимые законы.
  • +4
    А как же ссылки на демо?
  • +2
    Box2d прикольная библиотека. Не могу удержаться, чтобы не показать вот такой эксперимент, на котором я исследовал производительность Java JBox2D-Liquid-WC-Test.html :) Надеюсь никого сильно не оскорбит.
    • 0
      А что там с производительностью? :) Вполне шустро работает ваш пример.
      • 0
        Это смотря на чём запускать. Вы пробовали на планшете или телефоне с ARM или x86?… В принципе, сейчас уже всё достаточно быстро работает. Жидкость как и взрывную волну в Box2D можно моделировать большим количеством маленьких частиц. Думаю именно так делается в известных играх Sprinkle и Крокодильчик Свомпи.
        • 0
          А на каких телефонах можно запускать java-апплеты в браузере? Что-то мне еще ни разу не попадались такие.
          Запускал на x64, под icedtea.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.