three.js八叉树Octree扩展库介绍

参考资料:threejs中文网

threejs qq交流群:814702116

three.js八叉树Octree扩展库介绍

本章节学习八叉树Octree目的,是为了实现漫游的碰撞检测功能,比如遇到装障碍物被挡住、比如爬坡和上楼梯。

你可以打开本节课的工厂漫游案例体验测试。

本节课内容会比较多,不过大部分只是作为了解和扩展学习,你只需要你掌握下面3行代码即可。

js 复制代码
// 引入八叉树扩展库
import { Octree } from 'three/examples/jsm/math/Octree.js';
const worldOctree = new Octree();
// 分割模型,生成八叉树节点
worldOctree.fromGraphNode(模型对象);

八叉树基本原理解释

下面给大家简单介绍下八叉树概念,初学者不要求记住具体细节,先有个印象就行。

通过前面基础内容2.3. 网格模型(三角形概念)的学习,大家都知道网格模型Mesh本质是由三角形构成,三角形由顶点构成,这些三角形和自己的顶点数据分布在3D空间中。

javascript 复制代码
const geometry = new THREE.BoxGeometry(50,50,50);
console.log('顶点位置数据',geometry.attributes.position);
console.log('三角形顶点索引数据',geometry.index);

如果整个3D模型用一个长方体空间来表示,在三维空间xyz三个方向,都分割一次,这样就可以得到8个小的长方体子空间。

一个3D模型的三角形(顶点)分布在三维空间中,如果你用一个长方体来表示整个3d场景,当你分割为8个子空间的时候,每个子空间可以包含对应的三角形(顶点)数据。

每个子空间如果三角形(顶点)数量比较多,还可以继续分割,具体分割规则,你可以自定义,比如你可以规定,一个子空间包含的三角形数量只要大于8个就继续分割。这样一个个子空间可以构成一个树结构,整体来看,每个节点,分叉出来八个子节点。

项目引入Octree.js

Three.js在目录/examples/jsm/math/下提供了一个八叉树相关的扩展库Octree.js。 npm安装threejs情况下,Octree.js扩展库引入路径。

js 复制代码
// 引入/examples/jsm/math/目录下八叉树扩展库
import { Octree } from 'three/examples/jsm/math/Octree.js';

.html文件中,你也可以配置为其它任意路径引入方式

html 复制代码
<script type="importmap">
    {
		"imports": {
			"three": "../../../three.js/build/three.module.js",
            "three/addons/": "../../../three.js/examples/jsm/"
		}
	}
</script>
<script  type="module">
import { Octree } from 'three/addons/math/Octree.js';
</script>

生成八叉树.fromGraphNode()

实例化一个八叉树对象。

js 复制代码
const worldOctree = new Octree();

.fromGraphNode()的参数是模型对象,比如一个mesh,或者多个mesh构成的层级模型。

js 复制代码
const gltf = await loader.loadAsync("../地形.glb");
worldOctree.fromGraphNode(gltf.scene);

执行.fromGraphNode()会对模型进行分割,分割为一个一个的小的长方体空间,构成一个八叉树。

执行.fromGraphNode()会把一个3D模型,分割为8个子空间,每个子空间都包含对应的三角形或者说顶点数据,每个子空间还可以继续分割。

具体分割规则非常复杂,不要求掌握,如果你有兴趣可以阅读Octree.js的源码,比如Octree.js会根据三角形数量决定是否分割一个子空间,比如一个子空间包含的三角形数量小于等于8个就不在分割,当然你也可以修改规则,作为初学者,也不要求记住,先有个印象就行。

Octree.js文件中部分源码截取

js 复制代码
if ( len > 8 && level < 16 ) {
	subTrees[ i ].split( level + 1 );
}

浏览器控制台打印八叉树

浏览器控制台打印八叉树,查看分割的结果(不要求掌握,过一遍即可)。

js 复制代码
console.log('查看八叉树结构', worldOctree);
  • .box属性是包围盒Box3,描述当前分割的子空间位置和尺寸
  • .subTrees属性表示八叉树的子节点,类似threejs层级模型的children属性
  • 查看叶子结点(最后一层没有子对象的节点).triangles属性,可以看到包含的三角形数据

OctreeHelper可视化八叉树

Three.js在目录/examples/jsm/helpers/下提供了一个可视化八叉树相关的扩展库OctreeHelper.js

js 复制代码
import { OctreeHelper } from 'three/examples/jsm/helpers/OctreeHelper.js';
js 复制代码
//课程案例源码里面配置的路径
import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js';
js 复制代码
const helper = new OctreeHelper( worldOctree );
scene.add( helper );

八叉树与胶囊Capsule交叉计算

Capsule表示胶囊形状的几何体,具体说就是上面一个半球、中间一个圆柱、下面一个半球拼接构成的胶囊形状几何体。

下面给大家讲解胶囊形状几何体apsule与八叉树表示的3D模型进行交叉计算,你可以类比以前的射线交叉计算,虽然不同,都是都是交叉相关的计算。

项目引入胶囊碰撞体Capsule.js

Three.js在目录/examples/jsm/math/下提供了一个胶囊形状的几何体Capsule.js

npm安装threejs情况下,Capsule.js扩展库引入路径。

js 复制代码
// 引入/examples/jsm/math/目录下胶囊扩展库Capsule.js
import { Capsule } from 'three/examples/jsm/math/Capsule.js';

本课程案例源码.html里面自定义了新的路径。

js 复制代码
import { Capsule } from 'three/addons/math/Capsule.js';

创建胶囊几何体

创建胶囊几何体:让胶囊底部半球与y=0的平面刚好相切即可。

js 复制代码
const R = 0.4;//胶囊半径
const H = 1.7;//胶囊总高度
const start = new THREE.Vector3(0, R, 0);//底部半球球心坐标
const end = new THREE.Vector3(0, H - R, 0);//顶部半球球心坐标
const capsule = new Capsule(start, end, R);
console.log('capsule', capsule);

练习:Mesh可视化上面胶囊几何体

js 复制代码
// 可视化胶囊几何体
const capsuleHelper = CapsuleHelper(R, H);
model.add(capsuleHelper);
js 复制代码
function CapsuleHelper(R, H) {
    const group = new THREE.Group();
    const material = new THREE.MeshLambertMaterial({
        color: 0x00ffff,
        transparent: true,
        opacity: 0.5,
    });
    // 底部半球
    const geometry = new THREE.SphereGeometry(R, 25, 25, 0, 2 * Math.PI, 0, Math.PI / 2);
    geometry.rotateX(Math.PI);
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.y = R;
    group.add(mesh)
    // 顶部半球
    const geometry2 = new THREE.SphereGeometry(R, 25, 25, 0, 2 * Math.PI, 0, Math.PI / 2);
    const mesh2 = new THREE.Mesh(geometry2, material);
    mesh2.position.set(0, H - R, 0)
    group.add(mesh2)
    // 中间圆柱
    const h = H - 2 * R
    const geometry3 = new THREE.CylinderGeometry(R, R, h,32,1,true);
    geometry3.translate(0, h / 2+R,0)
    const mesh3 = new THREE.Mesh(geometry3, material);
    group.add(mesh3)
    return group;
}

平移胶囊几何体

平移胶囊碰撞体,使底部半球位于y=0的平面以下。

js 复制代码
capsule.translate(new THREE.Vector3(0, -R, 0));

可视化胶囊的模型对象同步平移

js 复制代码
capsuleHelper.position.y += -R;

换一种平移方式

js 复制代码
capsuleHelper.position.copy(capsule.start)
capsuleHelper.position.y -= R;

交叉计算

Octree.capsuleIntersect(capsule)可以计算Octree表示的3D模型与胶囊几何体capsule是否重合交叉,如果有重合交叉,返回交叉相关的信息,具体说就是在某个方向上交叉重合的深度是多少。

js 复制代码
// 碰撞检测:几何体交叉计算
// Octree表示的3D模型和Capsule交叉计算
const result = worldOctree.capsuleIntersect(capsule);
console.log('碰撞检测结果', result);
  • .depth交叉重合的深度
  • .normal深度对应的方向

先与八叉树里面的包围盒子节点进行交叉计算,在与相交叉的包围盒包含的三角形进行交叉计算。借助八叉树,相比较,for循环所有模型所有三角形分别进行交叉计算,更节约时间。

胶囊放在斜面上

平移胶囊放在斜面上,查看交叉重合计算结果。

js 复制代码
capsule.translate(new THREE.Vector3(0, 0, -3*H));
capsuleHelper.position.z += -3 * H;

你可以看到交叉方向不再是垂直于平面

  • .depth交叉重合的深度
  • .normal深度对应的方向

根据交叉碰撞数据,平移碰撞体

根据交叉碰撞数据,平移碰撞体,让胶囊碰撞体不在于八叉树对应模型重合。

  • .depth交叉重合的深度
  • .normal深度对应的方向

总结:.normal数据的特点就是让胶囊沿着.normal方向,平移.depth距离,就能刚好确保交叉重合深度为0

js 复制代码
// 根据碰撞结果平移胶囊碰撞体,使交叉重合深度为0
capsule.translate(result.normal.multiplyScalar(result.depth));
capsuleHelper.position.copy(capsule.start);
capsuleHelper.position.y -= R;

胶囊与楼梯交叉

js 复制代码
// 根据碰撞结果平移胶囊碰撞体,使交叉重合深度为0
capsule.translate(result.normal.multiplyScalar(result.depth));
capsuleHelper.position.copy(capsule.start);
capsuleHelper.position.y -= R;

平移后,不在于楼梯交叉,上升偏离地面了(如果运动起来,这个特点可以让胶囊产生上楼梯的效果,下节课会讲解)

胶囊向上平移,与地面没有接触,查看交叉计算结果

胶囊向上平移,与地面没有接触

js 复制代码
// 胶囊向上平移,与地面不交叉情况,查看计算结果
capsule.translate(new THREE.Vector3(0, R, 0));
capsuleHelper.position.y += R;

交叉计算结果返回值false

js 复制代码
const result = worldOctree.capsuleIntersect(capsule);
console.log('碰撞检测结果', result);
相关推荐
格瑞@_@3 天前
11.Three.js使用indexeddb前端缓存模型优化前端加载效率
前端·javascript·缓存·three.js·indexeddb缓存
谢小飞3 天前
我做了三把椅子原来纹理这样加载切换
前端·three.js
小白菜学前端3 天前
ThreeJS创建一个3D物体的基本流程
3d·three.js
茶老翁4 天前
1-初识Three.js
前端·three.js
莫石6 天前
搓绳子(直)
前端·数学·three.js
小白菜学前端6 天前
3d 添加辅助坐标器和轨道控制器
3d·three.js
孙_华鹏8 天前
threejs——实战中材质的应用
前端·three.js·数据可视化
天涯学馆11 天前
Three.js灯光阴影与动画交互
前端·unity3d·three.js
格瑞@_@15 天前
6.Three.js贴图与uv映射(uv坐标)理解和实践
javascript·three.js·贴图·uv
入秋丶24 天前
threejs - 包围盒和包围球
three.js