В библиотеке Cannon.js отсутствует встроенная модель ветра и
аэродинамического сопротивления. Физический движок оперирует силами,
импульсами, массами и ограничениями, но не содержит специализированных
атмосферных эффектов. Реализация ветра выполняется вручную через
добавление внешних сил к телам (Body) на каждом шаге
симуляции.
Подход основан на классической формуле сопротивления среды и векторной механике.
Основные элементы, участвующие в расчётах:
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 || ]
Сила направлена противоположно скорости.
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 — локальное смещение точки приложения.
Это создаёт:
Если скорость мала, сопротивление можно игнорировать:
if (speed < 0.1) return;
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;
}
}
В обновлении проверяется принадлежность тела зоне.
Реалистичная модель требует:
Без соблюдения масштабов аэродинамика становится нестабильной или визуально неправильной.
Аэродинамическое сопротивление — нелинейная сила. При большом
dt:
Использование фиксированного шага:
world.step(1/120);
повышает точность моделирования быстро движущихся тел.
Ручная реализация ветра и аэродинамического сопротивления в Cannon.js позволяет создавать сложные физические сцены с учётом среды, турбулентности, вихрей и крутящих моментов. Гибкость API движка обеспечивает полный контроль над расчётами и делает возможной как аркадную, так и инженерно приближенную физическую модель.