Интеграция cannon-es в Three.js-проект

Для интеграции физического движка cannon-es в проект на Three.js необходимо установить пакет через npm:

npm install three cannon-es

Импорт в модуле ES6 выполняется следующим образом:

import * as THREE from 'three';
import * as CANNON from 'cannon-es';

Создание сцены, камеры и рендерера Three.js остаётся стандартным:

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 10);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

Инициализация физического мира

Физический мир CANNON.World отвечает за симуляцию. Основные параметры:

  • gravity — гравитация вектором.
  • broadphase — алгоритм широкого поиска столкновений.
  • solver — система решения контактов.

Пример:

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

Для стабильности можно увеличить точность интеграции:

world.defaultContactMaterial.contactEquationStiffness = 1e7;
world.defaultContactMaterial.contactEquationRelaxation = 3;

Создание физических тел

Cannon-es использует Body и Shape для представления физических объектов. Типичные формы: Box, Sphere, Plane, Cylinder.

Пример создания пола и куба:

// Физический пол
const groundBody = new CANNON.Body({
    mass: 0, // масса 0 = статическое тело
    shape: new CANNON.Plane(),
    position: new CANNON.Vec3(0, 0, 0)
});
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);
world.addBody(groundBody);

// Физический куб
const boxShape = new CANNON.Box(new CANNON.Vec3(1, 1, 1));
const boxBody = new CANNON.Body({
    mass: 5,
    shape: boxShape,
    position: new CANNON.Vec3(0, 5, 0)
});
world.addBody(boxBody);

Соединение с Three.js-объектами

Для визуализации физики используется объект THREE.Mesh с геометрией и материалом. Объект привязывается к телу физического движка в анимационном цикле:

// Three.js-меш
const boxGeometry = new THREE.BoxGeometry(2, 2, 2);
const boxMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(boxMesh);

// Синхронизация в анимации
function animate() {
    requestAnimationFrame(animate);

    world.step(1/60); // шаг физики

    // Синхронизация позиции и ориентации
    boxMesh.position.copy(boxBody.position);
    boxMesh.quaternion.copy(boxBody.quaternion);

    renderer.render(scene, camera);
}
animate();

Коллизии и контактные события

Cannon-es поддерживает события столкновений через addEventListener:

boxBody.addEventListener('collide', (event) => {
    console.log('Столкновение с объектом:', event.body);
});

Каждое событие содержит информацию о другом теле и точке контакта. Это позволяет реализовать эффектные реакции на столкновения, например, изменение цвета или проигрывание звука.

Связка нескольких тел и ограничений

Для создания соединённых объектов используются Constraints:

  • CANNON.HingeConstraint — шарнирное соединение.
  • CANNON.DistanceConstraint — фиксация расстояния.
  • CANNON.LockConstraint — полная блокировка относительного движения.

Пример шарнирного соединения:

const hinge = new CANNON.HingeConstraint(boxBody, groundBody, {
    pivotA: new CANNON.Vec3(0, -1, 0),
    axisA: new CANNON.Vec3(0, 0, 1),
    pivotB: new CANNON.Vec3(0, 0, 0),
    axisB: new CANNON.Vec3(0, 0, 1)
});
world.addConstraint(hinge);

Интеграция с освещением и материалами

Для реалистичного отображения рекомендуется использовать THREE.MeshStandardMaterial или MeshPhysicalMaterial с источниками света:

const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
scene.add(directionalLight);

Физика и освещение работают отдельно, поэтому важно синхронизировать позиции и кватернионы объектов Three.js с телами Cannon-es при каждом кадре.

Оптимизация производительности

  1. Использовать broadphase SAPBroadphase для статичных или слабо подвижных сцен.
  2. Минимизировать количество тел с высокой массой или сложной геометрией.
  3. Шаг интеграции world.step держать фиксированным (например, 1/60).
  4. Синхронизировать только видимые объекты.

Полезные приёмы

  • Использование вспомогательных объектов Three.js для отладки:
const helper = new THREE.BoxHelper(boxMesh, 0x00ff00);
scene.add(helper);
  • Создание массивов тел и мешей для удобного обновления:
const objects = [];
objects.push({ mesh: boxMesh, body: boxBody });

objects.forEach(obj => {
    obj.mesh.position.copy(obj.body.position);
    obj.mesh.quaternion.copy(obj.body.quaternion);
});
  • Управление физикой через GUI (например, dat.GUI) позволяет менять массу, гравитацию или коэффициент трения в реальном времени.

Работа с сложными формами

Для нестандартных объектов можно использовать CANNON.ConvexPolyhedron:

const vertices = [
    new CANNON.Vec3(-1, -1, -1),
    new CANNON.Vec3(1, -1, -1),
    new CANNON.Vec3(1, 1, -1),
    new CANNON.Vec3(-1, 1, -1),
    new CANNON.Vec3(-1, -1, 1),
    new CANNON.Vec3(1, -1, 1),
    new CANNON.Vec3(1, 1, 1),
    new CANNON.Vec3(-1, 1, 1)
];
const faces = [
    [0,1,2,3],
    [4,5,6,7],
    [0,1,5,4],
    [2,3,7,6],
    [0,3,7,4],
    [1,2,6,5]
];
const shape = new CANNON.ConvexPolyhedron({ vertices, faces });
const body = new CANNON.Body({ mass: 5, shape });
world.addBody(body);

Эта методика позволяет интегрировать любые трехмерные модели, экспортированные из редакторов, в физическую симуляцию.