Применение физики на GLTF модель используя Cannon.js и Three.js

Рейтинг: 3Ответов: 2Опубликовано: 18.02.2023

Разрабатывая маленькую браузерную игрульку с закрытой картой столкнулся с проблемой. Не могу применить физику на 3д модель. Если быть вкратце то у меня есть TerrainMap в 3д модели GLTF, но уже со всеми текстурами и т.д, а так же есть персонаж который передвигается и у которого есть физика (падает, ходит и т.д). И все это на Cannon.js и Three.js.

Мне необходимо этот самый TerrainMap использовать в качестве карты для моей игры, но сейчас мой персонаж просто проходит сквозь карту. Нужно каким-то образом применить физику на объект что бы персонаж мог на ней передвигаться для последующих действий.

Использую Cannon.js и Three.js

UPD: Основная моя ошибка была в подборе неподходящей модели в которой нету пункта Geometry, всегда проверяйте в описании 3Д модели есть ли там подобный пункт. Обычно всегда если вы нашли 3Д, модель без этих значений то лучше уже даже не пытаться что либо делать, лишь потратите лишнее время и нервы.

Я пытался решить эту проблему около 2 недель, но в итоге решением оказалось смена модели на ту в которой есть входные данные Faces и Verticies. Спасибо большое всем кто отвечал и отдельное @Jack Owest

Ответы

▲ 2Принят

Для того что бы ваш персонаж мог передвигаться по поверхности(TerrainMap) с физикой, вам нужно создать твёрдое тело (Rigid Body) и коллайдер(Collider) для персонажа, а так же коллайдер для статической поверхности.

Rigid Body определяет физические свойства объекта, такие как масса, форма, положение, скорость и тд. Коллайдер определяет форму и размеры твердого тела и используется для обнаружения столкновений с другими объектами.

Начнём с поверхности:

var terrainMesh = gltf.scene.children[0];

// Создаём rigidbody
var terrainBody = new CANNON.Body({
   mass: 0 // масса 0 для статики
});

// Получаем массивы вершин и граней
var terrainVerts = terrainMesh.geometry.vertices.map(function(v) {
   return new CANNON.Vec3(v.x, v.y, v.z);
});
var terrainFaces = terrainMesh.geometry.faces.map(function(f) {
   return [f.a, f.b, f.c];
});

// Добавляем форму в тело
terrainBody.addShape(new CANNON.Trimesh(terrainVerts, terrainFaces));

//И пуляем в мир
world.addBody(terrainBody);

Для персонажа примерно тоже самое:

var characterMesh = gltf.scene.children[0];

// Создаём твердое тело
var characterBody = new CANNON.Body({
   mass: 1 // масса персонажа
});
    
// Создаём коллайдер
var characterBox = new THREE.Box3().setFromObject(characterMesh);
var characterSize = characterBox.getSize(new THREE.Vector3());
var characterShape = new CANNON.Box(new CANNON.Vec3(characterSize.x / 2, characterSize.y / 2, characterSize.z / 2));

// Добавляем форму в тело
characterBody.addShape(characterShape);

//И пуляем в мир
world.addBody(characterBody);

Теперь нам нужно сделать функцию обработки столкновений:

world.addEventListener('postStep', function() {
  var contacts = [];
  var normal = new CANNON.Vec3();
  var upAxis = new CANNON.Vec3(0, 1, 0);
  var threshold = 0.1;

  // Проверка столкновений между твердым телом TerrainMap и персонажем
  terrainBody.shapes.forEach(function(shape) {
    characterBody.shapes.forEach(function(otherShape) {
      if (shape.collisionResponse && otherShape.collisionResponse) {
        var contact = new CANNON.ContactMaterial(shape.material, otherShape.material, {
          friction: 0.1, // коэффициент трения
          restitution: 0.5, // коэффициент упругости
          contactEquationStiffness: 1e7, // жесткость уравнения контакта
          contactEquationRelaxation: 3, // время релаксации уравнения контакта
          contactEquationRegularizationTime: 3 // время регуляризации уравнения контакта
        });

        world.addContactMaterial(contact);

        var hit = false;
        var trimesh = shape;
        var box = otherShape;
        var boxPosition = characterBody.position;
        var boxQuat = characterBody.quaternion;

        // Проверка коллизии между коллайдерами персонажа и поверхности
        hit = trimesh.planeIntersect(box, boxPosition, boxQuat, normal);
        if (hit) {
          // Проверка, что столкновение произошло сверху
          if (normal.dot(upAxis) > threshold) {
            contacts.push({
              normal: normal,
              depth: -hit,
              contact: contact
            });
          }
        }
      }
    });
  });

  // Применение силы гравитации к персонажу
  characterBody.applyForce(new CANNON.Vec3(0, -9.8 * characterBody.mass, 0), characterBody.position);

  // Обработка столкновений между персонажем и поверхностью
  if (contacts.length > 0) {
    var c = contacts[0];
    var dir = c.normal.negate();

    characterBody.position.vadd(dir.mult(c.depth), characterBody.position);
  }
});

Обновление.

Если не находит геометрию она может быть в BufferGeometry. Вот код вытаскивающий вершины и грани от туда:

// Получаем массивы вершин и граней
var positionArray = terrainMesh.geometry.attributes.position.array;
var indexArray = terrainMesh.geometry.index.array;
var vertexCount = positionArray.length / 3;
var faceCount = indexArray.length / 3;

var terrainVerts = [];
for (var i = 0; i < vertexCount; i++) {
  var x = positionArray[i * 3];
  var y = positionArray[i * 3 + 1];
  var z = positionArray[i * 3 + 2];
  terrainVerts.push(new CANNON.Vec3(x, y, z));
}

var terrainFaces = [];
for (var i = 0; i < faceCount; i++) {
  var a = indexArray[i * 3];
  var b = indexArray[i * 3 + 1];
  var c = indexArray[i * 3 + 2];
  terrainFaces.push([a, b, c]);
}
▲ 1

Для того, чтобы применить физику на TerrainMap, вы должны создать физическое тело для вашей 3D-модели с использованием Cannon.js.

Вы можете использовать информацию о высоте из вашей карты высот для создания формы тела, которая будет соответствовать форме TerrainMap. Для этого можно использовать технику под названием heightfield, которая позволяет создавать тела в форме сетки с учетом высоты каждой точки.

Пример кода:

// Загружаем модель GLTF с помощью Three.js
const loader = new THREE.GLTFLoader();
loader.load('terrain.glb', (gltf) => {
  const terrainMesh = gltf.scene.children[0];

  // Создаем тело с помощью heightfield
  const terrainBody = new CANNON.Body({
    mass: 0, // масса 0 - значит, что объект неподвижен
    shape: new CANNON.Heightfield(terrainMesh.geometry.attributes.position.array, {
      elementSize: 1 // размер элемента в массиве вершин (в вашем случае это может быть размер ячейки)
    })
  });

  // Добавляем тело в мир физики
  world.addBody(terrainBody);
});

Затем вы можете добавить физическое тело в мир физики и обрабатывать столкновения между вашим персонажем и TerrainMap.

Пример кода обработки столкновения:

// Обрабатываем столкновение между персонажем и TerrainMap
world.addEventListener('collide', (event) => {
  const { body } = event;
  if (body === terrainBody) {
    // персонаж столкнулся с TerrainMap
    // здесь можно добавить логику для обработки столкновения
  }
});