随笔 - 9  文章 - 0 评论 - 3 阅读 - 11357
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

太长不看版

遍历场景地形里的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;
}

 

posted on   不化的冰  阅读(423)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示