Физический движок Cannon.js построен вокруг дискретного численного интегрирования и итерационного решателя ограничений. При работе с десятками объектов нагрузка остается умеренной, однако при сотнях и тысячах тел начинают проявляться узкие места:
Оптимизация требует понимания того, какие этапы занимают наибольшее время процессора и как их контролировать.
Broadphase уменьшает количество пар тел, передаваемых в точную фазу столкновений. Без него сложность проверки растёт как O(n²), что становится критично при большом числе объектов.
В Cannon.js доступны несколько стратегий:
NaiveBroadphase — проверяет все пары тел. Подходит
только для малого количества объектов.SAPBroadphase (Sweep and Prune) — сортировка по осям и
проверка пересечений интервалов.GridBroadphase (в некоторых сборках/форках) —
пространственное разбиение.Для сцен с сотнями динамических тел предпочтителен
SAPBroadphase:
world.broadphase = new CANNON.SAPBroadphase(world);
world.broadphase.axisIndex = 0; // оптимальная ось сортировки
Если большая часть движения происходит вдоль одной оси (например,
гоночная трасса вдоль X), установка axisIndex в
соответствующее значение снижает количество пересортировок.
Фильтрация через collision groups:
body.collisionFilterGroup = 1;
body.collisionFilterMask = 2;
Разделение тел по слоям позволяет исключить заведомо ненужные проверки и значительно уменьшить нагрузку.
Столкновения примитивов (Sphere, Box,
Plane) вычисляются быстрее, чем сложных форм
(ConvexPolyhedron, Trimesh). При большом
количестве тел следует:
Trimesh для динамических объектов,Пример упрощения:
const compound = new CANNON.Body({ mass: 5 });
compound.addShape(new CANNON.Box(new CANNON.Vec3(1, 1, 1)));
compound.addShape(new CANNON.Sphere(0.5), new CANNON.Vec3(1, 0, 0));
Составное тело часто работает быстрее, чем одна сложная выпуклая оболочка.
Основной параметр производительности:
world.solver.iterations = 10;
Чем больше итераций, тем стабильнее решение, но выше нагрузка. При большом числе тел:
Баланс выбирается экспериментально.
world.solver.tolerance = 0.01;
Увеличение tolerance позволяет завершать итерации
раньше, снижая нагрузку.
Рекомендуемый способ:
world.step(1 / 60, deltaTime, 3);
Параметры:
1/60 — фиксированный шаг,deltaTime — реальное время между кадрами,3 — максимум подшагов.Ограничение числа подшагов предотвращает лавинообразный рост вычислений при падении FPS.
При большом числе объектов допустимо:
1/30,Один из самых эффективных способов оптимизации:
world.allowSleep = true;
body.sleepSpeedLimit = 0.1;
body.sleepTimeLimit = 1;
Тело переходит в состояние сна, если:
sleepSpeedLimit,sleepTimeLimit.Спящие тела:
При сценах со множеством объектов (например, разрушения или склады ящиков) выигрыш может быть кратным.
Для неподвижных объектов:
mass: 0
Статические тела:
Если объект двигается по заданной траектории, предпочтительнее кинематический режим (в форках, таких как cannon-es):
body.type = CANNON.Body.KINEMATIC;
Это уменьшает вычислительную нагрузку по сравнению с динамическим телом.
При большом числе контактирующих тел:
Методы оптимизации:
contactEquationStiffness.timeStep.world.defaultContactMaterial.contactEquationStiffness = 1e7;
world.defaultContactMaterial.contactEquationRelaxation = 3;
Слишком высокие значения увеличивают нагрузку, поэтому требуется баланс.
Частое создание и удаление тел вызывает:
Рекомендуется:
body.velocity.setZero();
body.angularVelocity.setZero();
body.position.set(0, -1000, 0);
body.sleep();
Если известно, что объекты взаимодействуют только в пределах зоны:
Создание нескольких миров физики вместо одного большого:
const worldA = new CANNON.World();
const worldB = new CANNON.World();
Разделение сцены снижает квадратичный рост проверок.
Часто Cannon.js используется вместе с Three.js.
Физика может обновляться реже, чем рендер:
Это уменьшает нагрузку почти вдвое без существенной потери плавности.
Синхронизация должна происходить один раз за кадр:
mesh.position.copy(body.position);
mesh.quaternion.copy(body.quaternion);
Множественные обращения к физическим данным внутри кадра увеличивают накладные расходы.
На типичном клиентском устройстве:
Анализ производительности проводится через:
world.step,Пример замера:
const start = performance.now();
world.step(1/60);
const end = performance.now();
console.log("Physics step:", end - start);
Рост времени шага при увеличении числа тел показывает предел масштабируемости текущей конфигурации.
Trimesh для динамических объектов.Стабильная производительность достигается только комплексной оптимизацией всех этапов симуляции.