Ветер и аэродинамическое сопротивление вручную

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

Подход основан на классической формуле сопротивления среды и векторной механике.


Базовые принципы динамики в Cannon.js

Основные элементы, участвующие в расчётах:

  • CANNON.World — физический мир
  • CANNON.Body — физическое тело
  • CANNON.Vec3 — вектор
  • body.applyForce(force, worldPoint) — применение силы
  • body.applyImpulse(impulse, worldPoint) — применение импульса

В Cannon.js движение тела определяется уравнением:

[ F = m a]

где:

  • F — сумма сил,
  • m — масса,
  • a — ускорение.

Любая внешняя сила (включая ветер) должна добавляться на каждом шаге симуляции, иначе она действует только один кадр.


Простая модель ветра

Константный ветер

В простейшем случае ветер — это постоянный вектор силы, направленный в одну сторону.

const windForce = new CANNON.Vec3(10, 0, 0);

function applyWind(body) {
    body.applyForce(windForce, body.position);
}

Добавление в цикл симуляции:

world.addEventListener("postStep", () => {
    world.bodies.forEach(body => {
        if (body.mass > 0) {
            applyWind(body);
        }
    });
});

Особенности:

  • применяется только к динамическим телам (mass > 0);
  • сила действует постоянно;
  • отсутствует зависимость от формы и скорости объекта.

Такой метод подходит для аркадной физики, но не моделирует аэродинамику.


Аэродинамическое сопротивление

Сопротивление воздуха зависит от:

  • плотности среды
  • площади поперечного сечения
  • коэффициента сопротивления
  • относительной скорости тела

Классическая формула:

[ F_d = C_d A v^2]

где:

  • ρ — плотность воздуха,
  • C_d — коэффициент сопротивления,
  • A — площадь,
  • v — скорость относительно среды.

Векторная форма:

[ = - C_d A || ]

Сила направлена противоположно скорости.


Реализация сопротивления в Cannon.js

Подготовка параметров

const AIR_DENSITY = 1.225; // кг/м³

Функция расчёта сопротивления

function applyDrag(body, area, dragCoefficient) {
    const velocity = body.velocity.clone();

    const speed = velocity.length();
    if (speed === 0) return;

    const dragMagnitude = 0.5 * AIR_DENSITY * dragCoefficient * area * speed * speed;

    velocity.normalize();
    velocity.scale(-dragMagnitude, velocity);

    body.applyForce(velocity, body.position);
}

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

world.addEventListener("postStep", () => {
    world.bodies.forEach(body => {
        if (body.mass > 0) {
            applyDrag(body, 1.0, 1.2);
        }
    });
});

Учёт направления ветра

Если присутствует ветер, сопротивление рассчитывается относительно относительной скорости:

[ = - ]

Реализация

const windVelocity = new CANNON.Vec3(5, 0, 0);

function applyAerodynamics(body, area, dragCoefficient) {
    const relativeVelocity = body.velocity.vsub(windVelocity);
    const speed = relativeVelocity.length();

    if (speed === 0) return;

    const dragMagnitude = 0.5 * AIR_DENSITY * dragCoefficient * area * speed * speed;

    relativeVelocity.normalize();
    relativeVelocity.scale(-dragMagnitude, relativeVelocity);

    body.applyForce(relativeVelocity, body.position);
}

Теперь:

  • если объект летит по ветру — сопротивление уменьшается;
  • если против ветра — увеличивается.

Расчёт площади поперечного сечения

Для сферы

[ A = r^2]

function sphereArea(radius) {
    return Math.PI * radius * radius;
}

Для прямоугольного параллелепипеда

Приближённый расчёт можно выполнять по проекции скорости на локальные оси.

Упрощённый вариант — использовать максимальную грань:

function boxArea(width, height, depth) {
    return width * height;
}

Более точная модель требует:

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

Турбулентный ветер

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

function dynamicWind(time) {
    return new CANNON.Vec3(
        Math.sin(time) * 5,
        0,
        Math.cos(time * 0.5) * 3
    );
}

В обновлении:

let time = 0;

world.addEventListener("postStep", () => {
    time += world.dt;

    const wind = dynamicWind(time);

    world.bodies.forEach(body => {
        if (body.mass > 0) {
            applyAerodynamics(body, 1.0, 1.2, wind);
        }
    });
});

Требуется изменить функцию:

function applyAerodynamics(body, area, dragCoefficient, windVelocity) {
    const relativeVelocity = body.velocity.vsub(windVelocity);
    const speed = relativeVelocity.length();

    if (speed === 0) return;

    const dragMagnitude = 0.5 * AIR_DENSITY * dragCoefficient * area * speed * speed;

    relativeVelocity.normalize();
    relativeVelocity.scale(-dragMagnitude, relativeVelocity);

    body.applyForce(relativeVelocity, body.position);
}

Вихревые поля

Реализация вихря основана на вычислении тангенциальной силы вокруг центра.

function applyVortex(body, center, strength) {
    const direction = body.position.vsub(center);

    const distance = direction.length();
    if (distance === 0) return;

    direction.normalize();

    const tangent = new CANNON.Vec3(
        -direction.z,
        0,
        direction.x
    );

    tangent.scale(strength / distance, tangent);

    body.applyForce(tangent, body.position);
}

Характеристики:

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

Крутящий момент от ветра

Если центр давления не совпадает с центром масс, возникает вращение.

function applyWindWithTorque(body, windForce, offset) {
    const worldPoint = body.position.vadd(offset);
    body.applyForce(windForce, worldPoint);
}

Где offset — локальное смещение точки приложения.

Это создаёт:

  • парусный эффект,
  • раскачивание,
  • вращение объектов.

Оптимизация вычислений

1. Ограничение по скорости

Если скорость мала, сопротивление можно игнорировать:

if (speed < 0.1) return;

2. Использование коэффициента линейного демпфирования

Cannon.js имеет встроенное свойство:

body.linearDamping = 0.01;

Однако это:

  • не зависит от площади;
  • не зависит от плотности среды;
  • не учитывает направление ветра.

Поэтому встроенный демпфер — лишь приближённая модель вязкого трения.


Нелинейные модели сопротивления

Для медленных движений используется линейная модель:

[ F = -k v]

Для больших скоростей — квадратичная:

[ F = -k v |v|]

Комбинированная модель:

function applyAdvancedDrag(body, k1, k2) {
    const velocity = body.velocity.clone();
    const speed = velocity.length();

    if (speed === 0) return;

    const linear = k1 * speed;
    const quadratic = k2 * speed * speed;

    const total = linear + quadratic;

    velocity.normalize();
    velocity.scale(-total, velocity);

    body.applyForce(velocity, body.position);
}

Интеграция с игровыми сценами

Для транспорта

  • учитывается площадь фронтальной проекции;
  • добавляется сопротивление качению;
  • добавляется боковой снос при боковом ветре.

Для снарядов

  • рассчитывается баллистическая траектория;
  • сопротивление существенно влияет на дальность;
  • требуется уменьшение шага интеграции (world.step(dt)).

Для летательных аппаратов

  • рассчитывается подъёмная сила;
  • добавляется момент крена;
  • моделируется срыв потока при больших углах атаки.

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

Сильное сопротивление при больших скоростях может вызвать:

  • скачки ускорения,
  • нестабильность интеграции,
  • вибрации.

Методы стабилизации:

  • ограничение максимальной силы,
  • уменьшение dt,
  • использование fixedTimeStep.
world.step(1/60, deltaTime, 3);

Архитектура системы ветра

Рекомендуется выделять отдельный модуль:

class WindSystem {
    constructor(world) {
        this.world = world;
        this.airDensity = 1.225;
    }

    update(time) {
        const wind = dynamicWind(time);

        this.world.bodies.forEach(body => {
            if (body.mass > 0) {
                applyAerodynamics(body, 1.0, 1.2, wind);
            }
        });
    }
}

Подобная архитектура:

  • упрощает масштабирование,
  • позволяет добавлять зоны ветра,
  • облегчает тестирование.

Зональный ветер

Разные области сцены могут иметь различные параметры.

class WindZone {
    constructor(center, radius, velocity) {
        this.center = center;
        this.radius = radius;
        this.velocity = velocity;
    }

    affects(body) {
        return body.position.distanceTo(this.center) < this.radius;
    }
}

В обновлении проверяется принадлежность тела зоне.


Физическая правдоподобность

Реалистичная модель требует:

  • корректных единиц измерения;
  • согласованной массы тел;
  • масштабирования сцены (1 единица = 1 метр);
  • соответствия плотности среды.

Без соблюдения масштабов аэродинамика становится нестабильной или визуально неправильной.


Влияние шага интеграции

Аэродинамическое сопротивление — нелинейная сила. При большом dt:

  • траектория искажается;
  • объекты «пробивают» воздух;
  • возможна потеря энергии.

Использование фиксированного шага:

world.step(1/120);

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


Ручная реализация ветра и аэродинамического сопротивления в Cannon.js позволяет создавать сложные физические сцены с учётом среды, турбулентности, вихрей и крутящих моментов. Гибкость API движка обеспечивает полный контроль над расчётами и делает возможной как аркадную, так и инженерно приближенную физическую модель.