Импорт геометрии Three.js в ConvexPolyhedron

Работа с физикой в браузере с использованием Cannon.js часто требует преобразования визуальной геометрии из Three.js в физическую форму, совместимую с симуляцией. Одним из наиболее универсальных способов является использование формы ConvexPolyhedron, которая позволяет описывать объекты с произвольной выпуклой геометрией.


Основные концепции ConvexPolyhedron

ConvexPolyhedron представляет собой многогранник, определяемый вершинами и гранями:

  • vertices — массив точек, каждая из которых является объектом CANNON.Vec3. Эти точки определяют углы многогранника.
  • faces — массив массивов индексов вершин, каждая вложенная структура описывает одну грань.
  • normals (необязательно) — массив нормалей граней. Cannon.js способен автоматически вычислить нормали, но их можно задать вручную для оптимизации столкновений.
  • axes (необязательно) — используется для более точного расчёта столкновений через метод SAT (Separating Axis Theorem).

Ключевое ограничение — все вершины должны формировать выпуклый объект. В случае вогнутых форм необходимо разбивать их на несколько ConvexPolyhedron или использовать альтернативные формы (Trimesh).


Преобразование геометрии Three.js

Three.js предоставляет объект THREE.BufferGeometry, содержащий позиции вершин, нормали и индексы. Для ConvexPolyhedron необходимо:

  1. Извлечь позиции вершин

    const positionAttribute = geometry.getAttribute('position');
    const vertices = [];
    for (let i = 0; i < positionAttribute.count; i++) {
        const x = positionAttribute.getX(i);
        const y = positionAttribute.getY(i);
        const z = positionAttribute.getZ(i);
        vertices.push(new CANNON.Vec3(x, y, z));
    }
  2. Создать грани на основе индексов Если геометрия имеет индексы:

    const index = geometry.index;
    const faces = [];
    for (let i = 0; i < index.count; i += 3) {
        faces.push([
            index.getX(i),
            index.getX(i + 1),
            index.getX(i + 2)
        ]);
    }

    Для неиндексированной геометрии можно итерировать позиции каждые три вершины как одну грань.

  3. Обеспечить выпуклость ConvexPolyhedron работает корректно только с выпуклыми формами. Для сложных моделей следует использовать алгоритмы выпуклой оболочки, например QuickHull. Three.js предоставляет THREE.ConvexGeometry, который генерирует выпуклую оболочку из произвольного набора вершин.


Пример создания ConvexPolyhedron из Three.js геометрии

// Предположим, есть THREE.Mesh с геометрией
const geometry = mesh.geometry;

// Получение вершин
const vertices = [];
const positionAttribute = geometry.getAttribute('position');
for (let i = 0; i < positionAttribute.count; i++) {
    vertices.push(new CANNON.Vec3(
        positionAttribute.getX(i),
        positionAttribute.getY(i),
        positionAttribute.getZ(i)
    ));
}

// Получение граней
const faces = [];
if (geometry.index) {
    const index = geometry.index;
    for (let i = 0; i < index.count; i += 3) {
        faces.push([
            index.getX(i),
            index.getX(i + 1),
            index.getX(i + 2)
        ]);
    }
} else {
    for (let i = 0; i < vertices.length; i += 3) {
        faces.push([i, i + 1, i + 2]);
    }
}

// Создание ConvexPolyhedron
const shape = new CANNON.ConvexPolyhedron({
    vertices: vertices,
    faces: faces
});

Оптимизация и точность столкновений

  • Простые формы: если объект можно аппроксимировать кубом, сферой или цилиндром, использование этих примитивов будет быстрее, чем ConvexPolyhedron.

  • Масштабирование: если Three.js меш имеет масштаб (mesh.scale), его необходимо применить к вершинам перед созданием формы.

  • Центрирование: ConvexPolyhedron работает корректно, если объект центрирован в локальной системе координат. Для смещённых моделей рекомендуется вычитать центр масс:

    const center = new CANNON.Vec3();
    vertices.forEach(v => center.vadd(v, center));
    center.scale(1 / vertices.length, center);
    vertices.forEach(v => v.vsub(center, v));

Использование с телами Cannon.js

После создания формы её можно добавить к телу CANNON.Body:

const body = new CANNON.Body({
    mass: 5, // масса объекта
    shape: shape,
    position: new CANNON.Vec3(0, 10, 0)
});

world.addBody(body);

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


Тонкости работы с Three.js

  • Матрицы трансформации: перед извлечением вершин необходимо применить текущую матрицу mesh.updateMatrixWorld(); и использовать applyMatrix4(mesh.matrixWorld) к каждой вершине.
  • Triangulation: некоторые сложные грани должны быть разбиты на треугольники. Three.js автоматически генерирует треугольные индексы для BufferGeometry.
  • Совместимость с деформациями: если геометрия динамически изменяется (например, morphTarget или skinning), конверсия должна выполняться заново после каждой деформации.

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

  1. Минимизировать количество вершин — каждая лишняя вершина увеличивает стоимость расчёта столкновений.
  2. Использовать упрощённые модели для физики — визуальные детали можно оставить в Three.js, не влияя на физический расчёт.
  3. Предварительная генерация ConvexPolyhedron — создание формы заранее вместо пересчёта на каждом кадре значительно ускоряет симуляцию.

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