В физическом движке Cannon.js каждая динамическая сущность
представлена объектом Body, который участвует в вычислении
сил, импульсов и столкновений на каждом шаге симуляции. В процессе
расчёта к телу могут добавляться:
Все приложенные силы накапливаются во внутренних векторах
body.force и body.torque. После завершения
шага интеграции они должны быть очищены, иначе в следующем кадре будут
учитываться повторно, что приведёт к некорректной физике.
Каждый объект Body содержит следующие ключевые
свойства:
body.force // CANNON.Vec3 — суммарная сила
body.torque // CANNON.Vec3 — суммарный момент силы
В течение одного кадра различные подсистемы добавляют силы:
WorldapplyForceЭти силы суммируются.
Во время world.step() выполняется интеграция:
После интеграции силы должны быть сброшены.
Если очистка не выполняется, сила продолжит действовать бесконечно.
При вызове:
world.step(timeStep);
движок автоматически:
force и torqueЭто поведение встроено в цикл симуляции и в большинстве случаев не требует ручного вмешательства.
Схематично процесс выглядит так:
В некоторых случаях требуется принудительно сбросить накопленные силы. Для этого используется метод:
body.force.set(0, 0, 0);
body.torque.set(0, 0, 0);
Либо через внутренний метод:
body.clearForces();
Метод clearForces() выполняет:
Body.prototype.clearForces = function () {
this.force.set(0, 0, 0);
this.torque.set(0, 0, 0);
};
world.stepЕсли сила добавляется через:
body.applyForce(new CANNON.Vec3(10, 0, 0), body.position);
и не очищается, то на каждом кадре к уже существующей силе будет добавляться новая.
Результат:
Cannon.js использует полууявный метод Эйлера. Он чувствителен к большим значениям сил. Накопленные силы приводят к:
Важно различать:
| Тип воздействия | Метод | Очистка |
|---|---|---|
| Сила | applyForce |
Требует очистки |
| Импульс | applyImpulse |
Не накапливается |
Импульс немедленно изменяет скорость:
body.applyImpulse(impulseVector, contactPoint);
Он не сохраняется в body.force, поэтому не требует
сброса.
Гравитация в мире задаётся:
world.gravity.set(0, -9.82, 0);
На каждом шаге симуляции движок автоматически вычисляет гравитационную силу:
F = m * g
Эта сила добавляется к body.force перед интеграцией, а
затем очищается вместе с остальными силами.
Изменение world.gravity не требует ручного сброса сил —
пересчёт выполняется автоматически при следующем
step().
При использовании переменного шага:
world.step(fixedTimeStep, deltaTime, maxSubSteps);
Очистка сил выполняется после каждого внутреннего подшага (substep). Это предотвращает накопление сил при высокой частоте кадров.
Если создаётся собственный интегратор или вызывается
body.integrate() напрямую, необходимо вручную очищать
силы:
body.integrate(dt, true, false);
body.clearForces();
Cannon.js поддерживает три типа тел:
Body.DYNAMICBody.STATICBody.KINEMATICПолностью участвуют в физике. Силы накапливаются и очищаются автоматически.
Игнорируют силы. Векторы force и torque
фактически не используются.
Силы не влияют на движение. Скорость задаётся вручную. Очистка происходит, но фактически значения не участвуют в расчётах.
Обнуление векторов — дешёвая операция, так как используется повторное
применение существующего объекта Vec3. Новый объект не
создаётся, что исключает нагрузку на сборщик мусора.
Правильная практика:
body.force.set(0, 0, 0);
Неправильная практика:
body.force = new CANNON.Vec3(0, 0, 0);
Второй вариант создаёт новый объект и нарушает внутренние ссылки движка.
При обработке события:
body.addEventListener("collide", function (event) {
// реакция на столкновение
});
Движок уже применил силы реакции контакта. После завершения шага они будут очищены автоматически. Ручной сброс внутри обработчика не требуется и может привести к искажению результата.
Упрощённая последовательность:
Понимание этого порядка важно при создании сложных систем управления, например:
Сила должна добавляться до интеграции текущего шага.
Иногда требуется сохранить момент вращения, но убрать линейную силу:
body.force.set(0, 0, 0);
Или наоборот:
body.torque.set(0, 0, 0);
Это используется при моделировании:
Для отладки используется вывод:
console.log(body.force);
Если значение не равно нулю в начале нового кадра до добавления пользовательских сил — присутствует логическая ошибка.
Хорошая практика — проверка:
if (body.force.lengthSquared() > 0.0001) {
console.warn("Сила не была очищена");
}
Если остановить вызовы world.step(), но продолжать
добавлять силы, они будут накапливаться. При возобновлении симуляции
произойдёт резкий скачок.
Перед паузой или после неё целесообразно выполнить:
world.bodies.forEach(body => body.clearForces());
Очистка накопленных сил — обязательная часть корректной работы физической симуляции. Она:
Механизм реализован внутри шага симуляции, однако при кастомной
логике управления телами требуется осознанное управление очисткой
force и torque.