zl程序教程

您现在的位置是:首页 >  其他

当前栏目

three.js场景地形导出到物理引擎

2023-04-18 15:21:04 时间

太长不看版

遍历场景地形里的Mesh,从geometry里抽取index和position,通过这两个数组构建物理引擎里的Trimesh。

 

背景

最近在试制网页MMORPG,内核用最顺手的three.js,资产使用glTF Binary(.glb)文件载入场景。

需求

three.js虽然自带了OimoPhysics和包装,还包含了Ammo.js的包装,但两种包装都只能对三种特定几何体(BoxGeometry、SphereGeometry和IcosahedronGeometry)构造碰撞体,而GLTFLoader导入的是BufferGeometry,完全对不上。需要找到一种方法,在物理引擎中构造对应碰撞体。

环境

three.js r147

物理引擎(我用了cannon-es 0.20.0和@dimforge/rapier3d-compat 0.10.0)

过程

1. 基本几何体组合

……然后被美术否决了。嗯,我自己也不想这么搞_(:з」∠)_

2. Heightfield

在找到的物理引擎示例和演示里,除了构造基本几何体当做地面,剩下的都使用了Heightfield作为地形的构造方式。

然而这需要额外的高度图数据,生成、储存和读取都是需要解决的问题。

同时,考虑到将来有可能使用多层室内地形,这种方式需要额外工作才能支持多层结构。

最后没有使用。

3. Trimesh

看起来是唯一符合条件的方式,然而从哪里获取构造Trimesh的参数这个问题卡了很久。最后还是读three.js官方文档和glTF参考手册找到了线索。

物理引擎的Trimesh构造需要顶点坐标数组(vertices)和顶点索引数组(indices)。three.js的BufferGeometry里包含了这两项,只不过不怎么直观……

从glTF文件里读取的BufferGeometry,BufferGeometry.attributes里都有名为position的BufferAttribute,这个就是顶点坐标数组,通过BufferGeometry.getAttribute("position")就能拿到。

而顶点索引数组不在BufferGeometry.attributes里,就在BufferGeometry.index属性,也是BufferAttribute类型。

位置、旋转和缩放信息可以通过BufferGeometry所属Mesh的.getWorldPosition()、.getWorldQuaternion()和.getWorldScale()获得。(准确的说,这三个方法是Object3D的方法)

import { Quaternion, Vector3 } from "three";

let terrainModelRoot; // 地形资产根节点

// 读取模型资产......

terrainModelRoot.traverse(node => {
  if (node.isMesh && node.geometry.index && node.geometry.hasAttribute("position")) {
    // 几何体信息
    const geometry = node.geometry;
    // 顶点索引数组
    const indices = geometry.index.array;
    // 顶点坐标数组
    const vertices = geometry.getAttribute("position").array;
    // 缩放
    const scale = node.getWorldScale(new Vector3());
    // 位置
    const position = node.getWorldPosition(new Vector3());
    // 旋转(四元数)
    const quaternion = node.getWorldQuaternion(new Quaternion());

    // 构造物理世界Trimesh碰撞体......

  }
});

 

需要额外注意的是,一些物理引擎不支持缩放,比如Rapier3D。这种情况下就需要先对顶点坐标数组应用缩放,然后再构造Trimesh。

const positionAttribute = geometry.getAttribute("position");
const vertices = new Float32Array(positionAttribute.array.length);
const scale = node.getWorldScale(new Vector3());
for (let i = 0; i < positionAttribute.count; i++) {
  vertices[3 * i] = positionAttribute.array[3 * i] * scale.x;
  vertices[(3 * i) + 1] = positionAttribute.array[(3 * i) + 1] * scale.y;
  vertices[(3 * i) + 2] = positionAttribute.array[(3 * i) + 2] * scale.z;
}