Тестирование физических сцен без рендерера

Cannon.js предоставляет мощный движок для симуляции физики в JavaScript, полностью отделённый от визуализации. Создание мира начинается с инициализации объекта World, который управляет всеми физическими объектами, телами и контактами.

const world = new CANNON.World();
world.gravity.set(0, -9.82, 0); // Земная гравитация по оси Y
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 10;
  • gravity — вектор гравитации, задающий ускорение для всех тел.
  • broadphase — алгоритм предварительного поиска столкновений. Для небольших сцен подходит NaiveBroadphase, для больших — SAPBroadphase.
  • solver.iterations — число итераций для решения системы ограничений, влияет на стабильность и точность симуляции.

Создание тел и форм

Физические объекты в Cannon.js разделены на Body и Shape. Body отвечает за массу, положение и динамику, Shape — за форму для столкновений.

const sphereShape = new CANNON.Sphere(1); // радиус 1
const sphereBody = new CANNON.Body({
    mass: 5,
    position: new CANNON.Vec3(0, 10, 0),
    shape: sphereShape
});
world.addBody(sphereBody);

const groundShape = new CANNON.Plane();
const groundBody = new CANNON.Body({
    mass: 0,
    shape: groundShape
});
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);
world.addBody(groundBody);
  • Масса 0 делает тело статическим, оно не двигается под действием сил.
  • setFromEuler задаёт ориентацию плоскости в пространстве.

Симуляция без рендерера

Cannon.js не требует визуализации для тестирования. Основной метод — step, который шаг за шагом продвигает мир вперёд во времени.

const timeStep = 1.0 / 60.0; // 60 FPS

for (let i = 0; i < 300; i++) { // симуляция 5 секунд
    world.step(timeStep);
    console.log(`Время: ${(i+1)*timeStep}s, Позиция шара:`, sphereBody.position);
}
  • step(dt) обновляет физический мир на dt секунд.
  • После каждого шага можно анализировать состояние тел через position, velocity и quaternion.

Управление столкновениями

Cannon.js позволяет отслеживать коллизии между телами без графики. Для этого используется событие collide.

sphereBody.addEventListener('collide', function(event) {
    console.log('Столкновение с телом:', event.body.id, 'Сила удара:', event.contact.getImpactVelocityAlongNormal());
});
  • event.body — тело, с которым произошло столкновение.
  • contact.getImpactVelocityAlongNormal() возвращает величину удара по нормали контакта.

Применение сил и импульсов

Для тестирования реакции тел можно добавлять силы и импульсы напрямую.

// Сила постоянного действия
const force = new CANNON.Vec3(0, 100, 0);
sphereBody.applyForce(force, sphereBody.position);

// Мгновенный импульс
const impulse = new CANNON.Vec3(50, 0, 0);
sphereBody.applyImpulse(impulse, sphereBody.position);
  • applyForce(force, point) — добавляет силу в указанной точке, учитывая момент силы.
  • applyImpulse(impulse, point) — мгновенно изменяет скорость тела.

Работа с массивами тел

Для больших сцен удобно управлять телами через массивы и циклы, что упрощает тестирование сценариев.

const boxes = [];
for (let i = 0; i < 10; i++) {
    const box = new CANNON.Body({
        mass: 1,
        shape: new CANNON.Box(new CANNON.Vec3(1,1,1)),
        position: new CANNON.Vec3(i*2, 10, 0)
    });
    world.addBody(box);
    boxes.push(box);
}

// Симуляция и проверка всех коробок
for (let i = 0; i < 600; i++) {
    world.step(timeStep);
    boxes.forEach((box, idx) => {
        if (box.position.y < 0) console.log(`Коробка ${idx} упала`);
    });
}
  • Позволяет создавать сценарии падений, цепочек столкновений, проверку стабильности конструкций.

Измерение физических характеристик

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

boxes.forEach(box => {
    const kineticEnergy = 0.5 * box.mass * box.velocity.lengthSquared();
    console.log(`Кинетическая энергия: ${kineticEnergy.toFixed(2)}`);
});
  • velocity.lengthSquared() возвращает квадрат скорости, что упрощает вычисление кинетической энергии.
  • Аналогично можно вычислять потенциальную энергию через mass * g * height.

Совместимость с другими библиотеками

Cannon.js легко интегрируется с математическими и тестовыми библиотеками, такими как Mocha, Jest или Chai, для автоматизированного тестирования сценариев:

import { expect } from 'chai';

world.step(timeStep);
expect(sphereBody.position.y).to.be.closeTo(9.83, 0.01);
  • Позволяет проверять физическое поведение алгоритмически без необходимости графического рендеринга.
  • Сценарии можно запускать в CI/CD, что полезно для разработки игр и симуляторов.

Применение к сложным системам

Даже без визуализации Cannon.js поддерживает:

  • Joints и constraints — соединения тел для имитации шарниров, пружин, маятников.
  • Compound shapes — составные формы для сложных объектов.
  • Collision filters — маски столкновений, позволяющие тестировать выборочные взаимодействия.
const hinge = new CANNON.HingeConstraint(boxes[0], boxes[1], {
    pivotA: new CANNON.Vec3(1,0,0),
    axisA: new CANNON.Vec3(0,0,1)
});
world.addConstraint(hinge);
  • Позволяет строить физические механизмы, от роботов до разборных конструкций, без необходимости визуального контроля.

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

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

  • Использовать большие timeStep для быстрых сценариев, сохраняя стабильность.
  • Ограничивать solver.iterations при малых массах и небольших скоростях.
  • Применять NaiveBroadphase для небольших сцен, SAPBroadphase — для крупных.

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