Взрывы: радиальный импульс по телам в радиусе

Взрыв в игровой физике — это кратковременное радиальное воздействие, создающее импульс, распространяющийся от центра к окружающим телам. В библиотеке Ammo.js нет встроенного объекта «взрыв», поэтому его поведение моделируется вручную через применение импульсов к телам, попадающим в радиус воздействия.

Ключевые особенности реализации:

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

Принцип радиального импульса

Взрыв характеризуется следующими параметрами:

  • center — точка эпицентра;
  • radius — радиус действия;
  • force — базовая сила;
  • falloff — функция затухания.

Алгоритм расчёта:

  1. Получить все физические тела мира.
  2. Вычислить расстояние от центра взрыва до центра масс тела.
  3. Если расстояние меньше радиуса — применить импульс.
  4. Направление импульса — нормализованный вектор от центра к телу.
  5. Модуль импульса зависит от расстояния.

Получение тел из физического мира

В Ammo.js физический мир представлен объектом btDiscreteDynamicsWorld. Доступ к телам осуществляется через список коллизий или хранение собственных ссылок на btRigidBody.

Пример перебора всех объектов:

function applyExplosion(world, center, radius, force) {
    const numObjects = world.getNumCollisionObjects();
    
    for (let i = 0; i < numObjects; i++) {
        const obj = world.getCollisionObjectArray().at(i);
        const body = Ammo.castObject(obj, Ammo.btRigidBody);

        if (!body || body.isStaticObject()) continue;

        applyExplosionToBody(body, center, radius, force);
    }
}

Вычисление направления и расстояния

Позиция тела извлекается через его motionState:

function getBodyPosition(body) {
    const transform = new Ammo.btTransform();
    body.getMotionState().getWorldTransform(transform);
    return transform.getOrigin();
}

Далее рассчитывается вектор смещения:

function applyExplosionToBody(body, center, radius, force) {
    const position = getBodyPosition(body);

    const dx = position.x() - center.x();
    const dy = position.y() - center.y();
    const dz = position.z() - center.z();

    const distance = Math.sqrt(dx*dx + dy*dy + dz*dz);

    if (distance > radius) return;

Функции затухания силы

Существует несколько моделей ослабления импульса.

1. Линейное затухание

[ F = F_0 (1 - )]

const strength = force * (1 - distance / radius);

2. Квадратичное затухание

[ F = F_0 (1 - ()^2)]

Создаёт более мощный эффект у центра и слабый на границе.

3. Обратная квадратичная зависимость

Физически более реалистичная модель:

[ F = ]

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


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

В Ammo.js импульс применяется методом:

body.applyCentralImpulse(vector);

Полная реализация:

if (distance > 0.0001) {
    const nx = dx / distance;
    const ny = dy / distance;
    const nz = dz / distance;

    const strength = force * (1 - distance / radius);

    const impulse = new Ammo.btVector3(
        nx * strength,
        ny * strength,
        nz * strength
    );

    body.activate();
    body.applyCentralImpulse(impulse);
}

Метод activate() необходим для пробуждения тел, находящихся в состоянии сна.


Учёт массы

Импульс в физике связан с изменением скорости:

[ v = ]

В Ammo.js applyCentralImpulse автоматически учитывает массу тела.

Для компенсации чрезмерного влияния массы возможна нормализация:

const mass = 1 / body.getInvMass();
const adjustedStrength = strength * mass;

Однако это изменяет физическую правдоподобность поведения.


Взрыв с крутящим моментом

Для более реалистичного эффекта используется точечный импульс:

body.applyImpulse(impulse, relativePosition);

relativePosition — вектор от центра масс тела до точки приложения силы.

Это создаёт:

  • вращение объектов,
  • эффект переворачивания,
  • более кинематографичное поведение.

Оптимизация поиска тел в радиусе

Полный перебор всех объектов неэффективен при большом количестве тел. Возможные решения:

Использование AABB запроса

Создание временного btSphereShape и проверка пересечений через broadphase.

Ghost-объекты

Временный btGhostObject с формой сферы позволяет получить список перекрывающихся объектов:

const sphere = new Ammo.btSphereShape(radius);
const ghost = new Ammo.btGhostObject();
ghost.setCollisionShape(sphere);

После добавления в мир можно извлечь getNumOverlappingObjects().


Многошаговый взрыв

Иногда требуется не мгновенный импульс, а кратковременная ударная волна.

Реализация:

  • хранить список тел в радиусе;
  • на протяжении нескольких кадров уменьшать силу;
  • применять импульс частями.

Это создаёт эффект давления, а не мгновенного толчка.


Влияние гравитации и демпфирования

После импульса движение тел определяется:

  • гравитацией мира,
  • линейным и угловым демпфированием,
  • трением,
  • ограничениями (constraints).

Для усиления эффекта:

body.setDamping(0.05, 0.05);

Меньшее демпфирование позволяет объектам разлетаться дальше.


Вертикальное смещение импульса

Для более зрелищного результата вектор импульса можно модифицировать:

ny += 0.3;

Это создаёт лёгкий подъём тел вверх, имитируя давление горячего газа.


Разрушаемые объекты

В сочетании с:

  • проверкой превышения порога скорости,
  • удалением тела при превышении импульса,

можно реализовать разрушение.

Пример:

if (strength > breakThreshold) {
    world.removeRigidBody(body);
}

Работа с памятью Ammo

Объекты btVector3, btTransform, btSphereShape требуют ручного освобождения:

Ammo.destroy(impulse);
Ammo.destroy(transform);

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


Интеграция с визуальной частью

Взрыв обычно синхронизируется с:

  • частицами;
  • вспышкой света;
  • звуковым эффектом;
  • камерной вибрацией.

Физика должна выполняться в том же кадре, что и визуальный эффект, до вызова world.stepSimulation().


Тестирование и балансировка

Основные параметры для настройки:

  • радиус;
  • базовая сила;
  • тип затухания;
  • вертикальная поправка;
  • демпфирование.

Избыточная сила приводит к нестабильности симуляции и «выбросу» тел с огромной скоростью.

Оптимальный диапазон силы зависит от масштаба мира и массы объектов.


Схема полного процесса

  1. Определение центра взрыва.
  2. Поиск тел в радиусе.
  3. Вычисление расстояния и нормали.
  4. Расчёт силы с учётом затухания.
  5. Пробуждение тела.
  6. Применение импульса.
  7. Очистка временных объектов.

Итоговая структура функции

function explode(world, center, radius, force) {
    const numObjects = world.getNumCollisionObjects();

    for (let i = 0; i < numObjects; i++) {
        const obj = world.getCollisionObjectArray().at(i);
        const body = Ammo.castObject(obj, Ammo.btRigidBody);

        if (!body || body.isStaticObject()) continue;

        const transform = new Ammo.btTransform();
        body.getMotionState().getWorldTransform(transform);
        const pos = transform.getOrigin();

        const dx = pos.x() - center.x();
        const dy = pos.y() - center.y();
        const dz = pos.z() - center.z();

        const distance = Math.sqrt(dx*dx + dy*dy + dz*dz);
        if (distance > radius || distance === 0) {
            Ammo.destroy(transform);
            continue;
        }

        const nx = dx / distance;
        const ny = dy / distance;
        const nz = dz / distance;

        const strength = force * (1 - distance / radius);

        const impulse = new Ammo.btVector3(
            nx * strength,
            ny * strength,
            nz * strength
        );

        body.activate();
        body.applyCentralImpulse(impulse);

        Ammo.destroy(impulse);
        Ammo.destroy(transform);
    }
}

Такая реализация создаёт управляемый, масштабируемый и физически корректный эффект взрыва с радиальным импульсом в среде Ammo.js.