Разрушаемые объекты: дробление compound shape

Compound shape в Ammo.js — это физическая конструкция, позволяющая объединять несколько простых коллайдеров (box, sphere, cylinder, convex hull) в единое тело. Такая структура идеально подходит для создания разрушаемых объектов, где сложная геометрия не может быть адекватно смоделирована одной примитивной формой. Основная идея заключается в том, что каждый дочерний коллайдер добавляется с собственным смещением и ориентацией относительно центра массы общего объекта.

let compoundShape = new Ammo.btCompoundShape();

let boxShape = new Ammo.btBoxShape(new Ammo.btVector3(1, 1, 1));
let sphereShape = new Ammo.btSphereShape(0.5);

let transformBox = new Ammo.btTransform();
transformBox.setIdentity();
transformBox.setOrigin(new Ammo.btVector3(0, 0, 0));

let transformSphere = new Ammo.btTransform();
transformSphere.setIdentity();
transformSphere.setOrigin(new Ammo.btVector3(1.5, 0, 0));

compoundShape.addChildShape(transformBox, boxShape);
compoundShape.addChildShape(transformSphere, sphereShape);

Ключевой момент: compoundShape сам по себе не имеет геометрической массы — масса задается телу (rigid body), к которому он привязан.


Создание разрушаемых объектов

Разрушаемые объекты часто моделируются с использованием compound shape, где каждый фрагмент становится дочерним элементом. При ударе или столкновении можно разделять compound shape на отдельные тела, чтобы реализовать эффект разрушения.

Пошаговая схема:

  1. Определение структуры объекта. Каждая часть (фрагмент) описывается как отдельная форма.
  2. Создание btCompoundShape. Объединение всех фрагментов с нужными смещениями.
  3. Присвоение массы и создание rigid body.
  4. Реакция на столкновение. При превышении порога силы столкновения отдельные фрагменты выделяются в новые rigid bodies с сохранением текущей позиции и скорости.

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

function createDestructibleBlock(position) {
    let compound = new Ammo.btCompoundShape();

    let cube1 = new Ammo.btBoxShape(new Ammo.btVector3(0.5, 0.5, 0.5));
    let cube2 = new Ammo.btBoxShape(new Ammo.btVector3(0.5, 0.5, 0.5));

    let t1 = new Ammo.btTransform();
    t1.setIdentity();
    t1.setOrigin(new Ammo.btVector3(-0.5, 0, 0));

    let t2 = new Ammo.btTransform();
    t2.setIdentity();
    t2.setOrigin(new Ammo.btVector3(0.5, 0, 0));

    compound.addChildShape(t1, cube1);
    compound.addChildShape(t2, cube2);

    let mass = 2;
    let localInertia = new Ammo.btVector3(0, 0, 0);
    compound.calculateLocalInertia(mass, localInertia);

    let transform = new Ammo.btTransform();
    transform.setIdentity();
    transform.setOrigin(new Ammo.btVector3(position.x, position.y, position.z));

    let motionState = new Ammo.btDefaultMotionState(transform);
    let rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, compound, localInertia);
    let body = new Ammo.btRigidBody(rbInfo);

    physicsWorld.addRigidBody(body);

    return body;
}

Дробление при столкновении

Чтобы реализовать эффект разрушения, необходимо:

  • Отслеживать силу столкновения через contact callback.

  • При превышении порога силы:

    • Удалять оригинальный rigid body из мира.
    • Создавать новые rigid bodies для каждого дочернего shape с отдельной массой и скоростью, соответствующей текущему движению родителя.

Пример генерации новых тел:

function fractureCompoundBody(body) {
    let compound = body.getCollisionShape();
    let numChildren = compound.getNumChildShapes();

    let transform = new Ammo.btTransform();
    body.getMotionState().getWorldTransform(transform);

    let origin = transform.getOrigin();
    let rotation = transform.getRotation();

    for (let i = 0; i < numChildren; i++) {
        let childShape = compound.getChildShape(i);
        let childTransform = compound.getChildTransform(i);

        let worldTransform = new Ammo.btTransform();
        worldTransform.setIdentity();
        worldTransform.setOrigin(origin);
        worldTransform.setRotation(rotation);

        // Комбинируем трансформации родителя и дочернего shape
        worldTransform.op_mul(childTransform);

        let mass = 1;
        let localInertia = new Ammo.btVector3(0, 0, 0);
        childShape.calculateLocalInertia(mass, localInertia);

        let motionState = new Ammo.btDefaultMotionState(worldTransform);
        let rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, childShape, localInertia);
        let newBody = new Ammo.btRigidBody(rbInfo);

        physicsWorld.addRigidBody(newBody);
    }

    physicsWorld.removeRigidBody(body);
}

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


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

При работе с множеством фрагментов важно:

  • Использовать простые формы для коллизий — box или sphere вместо сложных convex hull, если это возможно.
  • Лимитировать число фрагментов для одного объекта.
  • Активировать sleeping для тел, которые не движутся, чтобы снизить нагрузку на симуляцию.
  • Разделять объекты на группы и применять столкновения только внутри группы, если логика игры позволяет.
body.setActivationState(Ammo.DISABLE_DEACTIVATION);

Использование этих техник позволяет поддерживать стабильную производительность даже при большом количестве разрушаемых объектов.


Работа с различными типами дочерних shape

Box, Sphere, Cylinder, Convex Hull — каждый тип shape имеет свои особенности:

  • BoxShape — простая и быстрая коллизия, хорошо подходит для кубических фрагментов.
  • SphereShape — минимальная вычислительная нагрузка, идеальна для дробления округлых объектов.
  • CylinderShape — удобна для колонн или труб.
  • ConvexHullShape — позволяет создавать сложную геометрию, но вычислительно дорогая, особенно при дроблении большого числа фрагментов.

Комбинирование разных типов shape в одном compound shape позволяет создавать реалистичные и разнообразные разрушаемые объекты.


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

Центр масс compound shape по умолчанию вычисляется автоматически. Для корректного поведения при разрушении и падении можно вручную смещать центр масс через setCenterOfMassTransform. Это особенно важно для объектов с асимметричной структурой:

let centerTransform = new Ammo.btTransform();
centerTransform.setIdentity();
centerTransform.setOrigin(new Ammo.btVector3(0, -0.5, 0));
compoundShape.setCenterOfMassTransform(centerTransform);

Такой подход позволяет контролировать устойчивость объекта и реалистичное распределение веса при дроблении.