Довідник Оновлено у липні 2026

Швидкий довідник Cannon-es API

Основні класи cannon-es — підтримуваного форку Cannon.js — для побудови фізики твердих тіл у браузері: World, Body, Shape, Material, Constraint, фільтрація колізій, події контактів, і точний патерн синхронізації тіл з мешами Three.js щокадру.

🌍 World

CANNON.World — контейнер верхнього рівня: гравітація, broadphase, солвер і списки тіл/constraints/contact materials живуть у ньому. Викликайте world.step(dt) раз на кадр анімації, щоб просунути симуляцію.

Властивість / метод Тип Призначення
gravity Vec3 Постійне прискорення, що діє на всі динамічні тіла
broadphase NaiveBroadphase | SAPBroadphase Стратегія відсіювання пар перед narrow phase
solver.iterations number Кількість ітерацій PGS-солвера за крок (за замовч. 10)
defaultContactMaterial ContactMaterial Тертя/пружність, коли для пари не задано окремий материал
world.addBody(body) метод Зареєструвати Body у світі
world.addConstraint(c) метод Зареєструвати Constraint (шарнір)
world.step(dt, time, maxSubSteps) метод Просунути симуляцію на dt секунд (фіксований крок + інтерполяція)
import * as CANNON from 'cannon-es';

const world = new CANNON.World({
  gravity: new CANNON.Vec3(0, -9.82, 0),
});
world.broadphase = new CANNON.SAPBroadphase(world);
world.solver.iterations = 10;

// Виклик step() з фіксованим кроком у циклі рендеру
const fixedTimeStep = 1 / 60;
function animate() {
  requestAnimationFrame(animate);
  world.step(fixedTimeStep);
  renderer.render(scene, camera);
}

📦 Body

CANNON.Body представляє одне тверде тіло: масу, позицію, кватерніон (орієнтацію), швидкість, кутову швидкість і одну чи кілька приєднаних форм. Тіло з mass: 0статичне — нескінченна маса, ніколи не рухається, але бере участь у колізіях.

Властивість Тип Примітки
mass number 0 = статичне, >0 = динамічне
position Vec3 Центр мас у світових координатах
quaternion Quaternion Орієнтація — копіюйте напряму у mesh.quaternion
velocity / angularVelocity Vec3 Лінійна / кутова швидкість у світових координатах
type Body.DYNAMIC | STATIC | KINEMATIC KINEMATIC тіла рухаються скриптом, не реагують на сили
linearDamping / angularDamping number 0–1 Загасання швидкості за крок (наближення опору повітря)
fixedRotation boolean Фіксує орієнтацію — корисно для персонажів/предметів, які не мають перекидатися
allowSleep / sleepSpeedLimit boolean / number Тіла у спокої «засинають» — солвер пропускає їх, доки їх не потривожать
ccdSpeedThreshold number Вмикає безперервне виявлення колізій вище цієї швидкості (запобігає тунелюванню)
const body = new CANNON.Body({
  mass: 1,
  position: new CANNON.Vec3(0, 5, 0),
  shape: new CANNON.Sphere(0.5),
  linearDamping: 0.01,
});
body.applyForce(new CANNON.Vec3(0, 0, 50), body.position);
body.applyImpulse(new CANNON.Vec3(5, 0, 0), body.position);
world.addBody(body);

🔷 Форми (Shapes)

Одне Body може нести кілька форм (складені тіла), кожна з опціональним локальним offset і orientation через body.addShape(shape, offset, quaternion).

Форма Конструктор Застосування
Box new Box(new Vec3(hx,hy,hz)) Половини розмірів — ящики, стіни, платформи
Sphere new Sphere(radius) Найдешевша перевірка narrow phase — м'ячі, частинки
Cylinder new Cylinder(rTop, rBottom, h, segments) Бочки, колеса (у парі з HingeConstraint)
ConvexPolyhedron new ConvexPolyhedron({vertices, faces}) Довільні опуклі меші — імпорт з Three.js BufferGeometry
Trimesh new Trimesh(vertices, indices) Тільки статична неопукла геометрія — рельєф, меші рівня
Heightfield new Heightfield(matrix, {elementSize}) Рельєф на основі сітки — дешевше за Trimesh для ландшафтів
Plane new Plane() Нескінченна статична площина — земля, невидимі стіни
Particle new Particle() Точкова маса нульового розміру — вузли тканини/мотузки
Неопуклі форми — лише статичні

Trimesh і Heightfield можна приєднати лише до тіл з mass: 0 — SAT-базований narrow phase cannon-es вимагає опуклості для динамічних тіл. Для динамічного неопуклого об'єкта розкладіть його на кілька опуклих форм на одному складеному тілі (наприклад, за допомогою інструмента декомпозиції на опуклі оболонки).

🧪 Материали та ContactMaterial

Material — це просто іменована мітка на формі/тілі; фактичні числа тертя та пружності живуть у ContactMaterial, який поєднує два материали разом.

const ice = new CANNON.Material('ice');
const rubber = new CANNON.Material('rubber');

const iceRubberContact = new CANNON.ContactMaterial(ice, rubber, {
  friction: 0.02,      // низьке — вільно ковзає
  restitution: 0.3,    // 0 = без відскоку, 1 = ідеально пружний
  contactEquationStiffness: 1e8,
  contactEquationRelaxation: 3,
});
world.addContactMaterial(iceRubberContact);

const puck = new CANNON.Body({ mass: 1, material: rubber, shape: new CANNON.Cylinder(0.4,0.4,0.1,16) });
const rink = new CANNON.Body({ mass: 0, material: ice, shape: new CANNON.Plane() });

🔗 Constraints (обмеження)

Constraints (шарніри) обмежують відносний рух двох тіл. Усі constraints додаються до світу через world.addConstraint(c).

Constraint Поведінка
PointToPointConstraint Прикріплює локальну точку A до локальної точки B — кулькові шарніри, мотузки
HingeConstraint Одна обертальна ступінь свободи навколо спільної осі — двері, колеса, плечі маятника
DistanceConstraint Фіксована відстань між двома тілами — жорсткі сегменти мотузки
LockConstraint Повністю зварює два тіла разом (без відносного руху)
ConeTwistConstraint Кульковий шарнір з обмеженням кута конуса — плечі/стегна ragdoll
// Маятник: шарнір, що з'єднує ящик з фіксованою точкою підвісу
const anchor = new CANNON.Body({ mass: 0 });
anchor.position.set(0, 5, 0);
world.addBody(anchor);

const hinge = new CANNON.HingeConstraint(anchor, bobBody, {
  pivotA: new CANNON.Vec3(0, 0, 0),
  pivotB: new CANNON.Vec3(0, 1.5, 0),
  axisA:  new CANNON.Vec3(0, 0, 1),
  axisB:  new CANNON.Vec3(0, 0, 1),
});
world.addConstraint(hinge);

🎯 Групи та маски колізій

Кожна форма несе collisionFilterGroup (до якої групи вона належить) і collisionFilterMask (з якими групами вона має перевірятись) — обидва бітові маски. Дві форми зіштовхуються тільки якщо (A.group & B.mask) і (B.group & A.mask) обидва ненульові.

const GROUP_PLAYER  = 1;   // 0b0001
const GROUP_ENEMY   = 2;   // 0b0010
const GROUP_TERRAIN = 4;   // 0b0100
const GROUP_TRIGGER = 8;   // 0b1000 — без фізичної реакції, лише події

playerBody.collisionFilterGroup = GROUP_PLAYER;
playerBody.collisionFilterMask  = GROUP_ENEMY | GROUP_TERRAIN | GROUP_TRIGGER;

// Вороги зіштовхуються з рельєфом, але проходять крізь одне одного
enemyBody.collisionFilterGroup = GROUP_ENEMY;
enemyBody.collisionFilterMask  = GROUP_PLAYER | GROUP_TERRAIN;

📡 Події

Подія Спрацьовує на Дані
'collide' body { body, target, contact }
'sleep' / 'wakeup' body Тіло увійшло/вийшло зі стану сну
'postStep' world Спрацьовує після кожного world.step() — гарне місце для зчитування оновлених позицій
playerBody.addEventListener('collide', (e) => {
  const impactSpeed = e.contact.getImpactVelocityAlongNormal();
  if (impactSpeed > 5) playLandingSound(impactSpeed);
});

⚙️ Синхронізація Cannon-es ↔ Three.js

Cannon-es не має власної концепції рендерингу — щокадру копіюйте position і quaternion кожного тіла напряму у відповідний Three.js mesh. Обидві бібліотеки використовують однакову конвенцію осей і структуру кватерніона, тож жодних перетворень не потрібно.

const bodies = []; // { body: CANNON.Body, mesh: THREE.Mesh }

function addBox(size, mass, position) {
  const shape = new CANNON.Box(new CANNON.Vec3(size.x/2, size.y/2, size.z/2));
  const body  = new CANNON.Body({ mass, shape, position: new CANNON.Vec3(...position) });
  world.addBody(body);

  const mesh = new THREE.Mesh(
    new THREE.BoxGeometry(size.x, size.y, size.z),
    new THREE.MeshStandardMaterial({ color: 0x60a5fa }),
  );
  scene.add(mesh);

  bodies.push({ body, mesh });
  return body;
}

function animate() {
  requestAnimationFrame(animate);
  world.step(1/60);

  // Синхронізувати кожен меш з фізичним станом його тіла
  for (const { body, mesh } of bodies) {
    mesh.position.copy(body.position);
    mesh.quaternion.copy(body.quaternion);
  }

  renderer.render(scene, camera);
}
Розв'яжіть крок фізики і частоту рендеру

world.step(dt, deltaTime, maxSubSteps) приймає фактичний минулий час другим аргументом і виконає додаткові фіксовані під-кроки внутрішньо, щоб «наздогнати» — викликайте його як world.step(1/60, realDeltaSeconds, 5), щоб фізика лишалась детермінованою навіть при різній частоті оновлення екрана (60Гц проти 144Гц проти зависання вкладки).

⚠️ Типові помилки