Расчёт взрывного импульса по расстоянию

В библиотеке Oimo.js взрыв моделируется как мгновенное или кратковременное воздействие импульса на группу тел в заданной области пространства. Основная задача — вычислить величину импульса в зависимости от расстояния до центра взрыва и корректно применить его к физическим телам.

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

  • Сила действует в течение времени и влияет на ускорение.
  • Импульс изменяет скорость мгновенно: [ = m ]

В Oimo.js импульс применяется напрямую через изменение линейной скорости или через метод добавления импульса к телу.


Базовая формула затухания по расстоянию

Пусть:

  • ( R ) — радиус взрыва
  • ( d ) — расстояние от центра взрыва до тела
  • ( F_0 ) — максимальная сила (или импульс) в центре
  • ( F(d) ) — сила на расстоянии ( d )

Условие действия: [ d R]

Наиболее распространённые модели затухания:

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

[ F(d) = F_0 (1 - )]

Преимущества:

  • Простота вычисления
  • Подходит для аркадной физики

Недостатки:

  • Нереалистичная энергетическая модель

2. Квадратичное затухание (обратный квадрат)

[ F(d) = ]

Где ( ) — малое число для защиты от деления на ноль.

Более физически корректная модель, имитирующая распространение энергии в пространстве.


3. Комбинированная модель с нормализацией радиуса

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

Даёт мягкое и реалистичное затухание.


Получение тел в радиусе взрыва в Oimo.js

В Oimo.js мир физики представлен объектом World. Тела хранятся в списке, который можно перебрать.

Пример поиска тел:

function getBodiesInRadius(world, center, radius) {
    const bodies = [];
    let body = world.getBodyList();
    
    while (body) {
        const pos = body.getPosition();
        const dx = pos.x - center.x;
        const dy = pos.y - center.y;
        const dz = pos.z - center.z;
        
        const distanceSq = dx*dx + dy*dy + dz*dz;
        
        if (distanceSq <= radius * radius) {
            bodies.push(body);
        }
        
        body = body.getNext();
    }
    
    return bodies;
}

Использование квадрата расстояния избавляет от лишнего вычисления квадратного корня.


Расчёт направления импульса

Импульс должен быть направлен от центра взрыва к телу.

[ = ]

Нормализация обязательна для корректной масштабируемости силы.

function computeDirection(bodyPos, explosionPos) {
    const dx = bodyPos.x - explosionPos.x;
    const dy = bodyPos.y - explosionPos.y;
    const dz = bodyPos.z - explosionPos.z;

    const length = Math.sqrt(dx*dx + dy*dy + dz*dz) || 1;

    return {
        x: dx / length,
        y: dy / length,
        z: dz / length,
        distance: length
    };
}

Вычисление импульса

Полная схема:

  1. Вычислить расстояние.
  2. Проверить, находится ли тело внутри радиуса.
  3. Рассчитать коэффициент затухания.
  4. Умножить направление на величину импульса.
function computeExplosionImpulse(distance, radius, maxForce) {
    if (distance > radius) return 0;

    const t = distance / radius;
    return maxForce * (1 - t) * (1 - t);
}

Применение импульса в Oimo.js

В Oimo.js можно использовать:

  • applyImpulse
  • либо напрямую изменить линейную скорость

Корректный способ:

function applyExplosion(body, explosionPos, radius, maxForce) {
    const bodyPos = body.getPosition();
    const result = computeDirection(bodyPos, explosionPos);

    if (result.distance > radius) return;

    const force = computeExplosionImpulse(
        result.distance,
        radius,
        maxForce
    );

    const impulse = new OIMO.Vec3(
        result.x * force,
        result.y * force,
        result.z * force
    );

    body.applyImpulse(bodyPos, impulse);
}

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


Учёт массы тела

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

[ v = ]

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

const mass = body.getMass();
const scaledForce = baseForce / Math.max(mass, 0.1);

Это позволяет лёгким объектам отлетать сильнее.


Вертикальная составляющая взрыва

Для создания «подбрасывающего» эффекта добавляют смещение по оси Y:

impulse.y += liftFactor;

Или модифицируют направление:

result.y += 0.5;

После этого вектор нужно снова нормализовать.


Ограничение максимальной скорости

Взрыв может придать телам чрезмерную скорость. Ограничение:

const maxVelocity = 50;
const velocity = body.getLinearVelocity();

if (velocity.length() > maxVelocity) {
    velocity.normalize().scaleEq(maxVelocity);
    body.setLinearVelocity(velocity);
}

Это предотвращает нестабильность симуляции.


Радиальный falloff с плавной кривой

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

[ F(d) = F_0 smoothstep(1 - t)]

Где:

function smoothstep(x) {
    return x * x * (3 - 2 * x);
}

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


Влияние времени шага симуляции

Если взрыв реализуется как сила в течение нескольких кадров:

[ F_{per_frame} = ]

Где Δt — шаг симуляции world.step(dt).

При малом dt сила должна быть больше, иначе эффект ослабнет.


Импульс с учётом линии видимости

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

  1. Выпустить луч из центра взрыва к телу.
  2. Если пересечение произошло раньше тела — импульс не применять.

Это предотвращает прохождение взрывной волны сквозь препятствия.


Создание универсальной функции взрыва

function explode(world, center, radius, maxForce) {
    let body = world.getBodyList();

    while (body) {
        if (body.isStatic()) {
            body = body.getNext();
            continue;
        }

        applyExplosion(body, center, radius, maxForce);
        body = body.getNext();
    }
}

Такая архитектура позволяет:

  • вызывать взрыв в любой точке сцены
  • масштабировать мощность
  • комбинировать разные модели затухания

Числовая стабильность

Для предотвращения ошибок:

  • использовать distanceSq при проверке радиуса
  • избегать деления на ноль
  • ограничивать максимальные значения импульса
  • учитывать масштаб сцены (1 единица = 1 метр)

При слишком больших силах возможны:

  • туннелирование
  • нарушение устойчивости решателя
  • рассыпание ограничений

Баланс реализма и производительности

Наиболее производительная схема:

  1. Проверка по квадрату расстояния.
  2. Нормализация только после попадания в радиус.
  3. Использование квадратичного falloff.
  4. Применение импульса один раз за кадр.

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


Расчёт взрывного импульса в Oimo.js сводится к трём ключевым этапам: корректное определение расстояния, выбор модели затухания и физически грамотное применение импульса к телу. Правильная реализация обеспечивает устойчивость симуляции, контролируемость игрового процесса и предсказуемую динамику объектов.