Очистка накопленных сил

В физическом движке Cannon.js каждая динамическая сущность представлена объектом Body, который участвует в вычислении сил, импульсов и столкновений на каждом шаге симуляции. В процессе расчёта к телу могут добавляться:

  • постоянные силы (гравитация, тяга, сопротивление),
  • мгновенные силы (удар, толчок),
  • крутящие моменты (torque),
  • импульсы.

Все приложенные силы накапливаются во внутренних векторах body.force и body.torque. После завершения шага интеграции они должны быть очищены, иначе в следующем кадре будут учитываться повторно, что приведёт к некорректной физике.


Внутренние поля накопления

Каждый объект Body содержит следующие ключевые свойства:

body.force     // CANNON.Vec3 — суммарная сила
body.torque    // CANNON.Vec3 — суммарный момент силы

Принцип работы

  1. В течение одного кадра различные подсистемы добавляют силы:

    • гравитация из World
    • пользовательские вызовы applyForce
    • силы столкновений
  2. Эти силы суммируются.

  3. Во время world.step() выполняется интеграция:

    • обновляется скорость
    • обновляется позиция
  4. После интеграции силы должны быть сброшены.

Если очистка не выполняется, сила продолжит действовать бесконечно.


Автоматическая очистка при шаге симуляции

При вызове:

world.step(timeStep);

движок автоматически:

  • вычисляет все силы
  • интегрирует движение
  • обнуляет force и torque

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

Схематично процесс выглядит так:

  1. Суммирование сил
  2. Интеграция (Euler или Semi-implicit Euler)
  3. Обнуление накопленных векторов

Ручная очистка сил

В некоторых случаях требуется принудительно сбросить накопленные силы. Для этого используется метод:

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
  • при выполнении нескольких интеграций внутри одного кадра
  • при сбросе состояния сцены
  • при реализации паузы симуляции

Проблемы при отсутствии очистки

1. Накопление силы в течение кадров

Если сила добавляется через:

body.applyForce(new CANNON.Vec3(10, 0, 0), body.position);

и не очищается, то на каждом кадре к уже существующей силе будет добавляться новая.

Результат:

  • экспоненциальный рост скорости
  • потеря стабильности
  • «взрыв» симуляции

2. Нарушение устойчивости интегратора

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.DYNAMIC
  • Body.STATIC
  • Body.KINEMATIC

DYNAMIC

Полностью участвуют в физике. Силы накапливаются и очищаются автоматически.

STATIC

Игнорируют силы. Векторы force и torque фактически не используются.

KINEMATIC

Силы не влияют на движение. Скорость задаётся вручную. Очистка происходит, но фактически значения не участвуют в расчётах.


Оптимизация очистки

Обнуление векторов — дешёвая операция, так как используется повторное применение существующего объекта Vec3. Новый объект не создаётся, что исключает нагрузку на сборщик мусора.

Правильная практика:

body.force.set(0, 0, 0);

Неправильная практика:

body.force = new CANNON.Vec3(0, 0, 0);

Второй вариант создаёт новый объект и нарушает внутренние ссылки движка.


Связь очистки сил с событиями столкновений

При обработке события:

body.addEventListener("collide", function (event) {
    // реакция на столкновение
});

Движок уже применил силы реакции контакта. После завершения шага они будут очищены автоматически. Ручной сброс внутри обработчика не требуется и может привести к искажению результата.


Порядок вычислений внутри шага

Упрощённая последовательность:

  1. Очистка контактных уравнений
  2. Добавление гравитации
  3. Добавление пользовательских сил
  4. Решение ограничений
  5. Интеграция скоростей
  6. Интеграция позиций
  7. Очистка сил

Понимание этого порядка важно при создании сложных систем управления, например:

  • реактивных двигателей
  • магнитных полей
  • систем притяжения

Сила должна добавляться до интеграции текущего шага.


Частичная очистка

Иногда требуется сохранить момент вращения, но убрать линейную силу:

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.